Skip to content
Open
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
2 changes: 2 additions & 0 deletions crates/rspack/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,8 @@ impl CompilerOptionsBuilder {
let entry_options = EntryOptions {
name: Some(name),
runtime: desc.runtime.map(EntryRuntime::String),
worker: None,
worklet: None,
chunk_loading: desc.chunk_loading,
wasm_loading: desc.wasm_loading,
async_chunks: desc.async_chunks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ BuilderContext {
"main",
),
runtime: None,
worker: None,
worklet: None,
chunk_loading: None,
wasm_loading: None,
async_chunks: None,
Expand Down
2 changes: 2 additions & 0 deletions crates/rspack_binding_api/src/options/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ impl From<JsEntryOptions> for EntryOptions {
Self {
name: value.name,
runtime: value.runtime.map(|r| JsEntryRuntimeWrapper(r).into()),
worker: None,
worklet: None,
chunk_loading: value.chunk_loading.map(Into::into),
wasm_loading: value.wasm_loading.map(Into::into),
async_chunks: value.async_chunks,
Expand Down
4 changes: 4 additions & 0 deletions crates/rspack_core/src/chunk_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ impl Display for EntryRuntime {
pub struct EntryOptions {
pub name: Option<String>,
pub runtime: Option<EntryRuntime>,
pub worker: Option<bool>,
pub worklet: Option<bool>,
pub chunk_loading: Option<ChunkLoading>,
pub wasm_loading: Option<WasmLoading>,
pub async_chunks: Option<bool>,
Expand All @@ -438,6 +440,8 @@ impl EntryOptions {
}
merge_field!(name);
merge_field!(runtime);
merge_field!(worker);
merge_field!(worklet);
merge_field!(chunk_loading);
merge_field!(wasm_loading);
merge_field!(async_chunks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ impl Task<ExecutorTaskContext> for ExecuteTask {
options: Box::new(EntryOptions {
name: Some("build time".into()),
runtime: Some("runtime".into()),
worker: None,
worklet: None,
chunk_loading: Some(crate::ChunkLoading::Disable),
wasm_loading: Some(crate::WasmLoading::Disable),
async_chunks: Some(false),
Expand Down
76 changes: 56 additions & 20 deletions crates/rspack_core/src/runtime_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,8 @@ fn dojang_empty_function(compiler_options: &Arc<CompilerOptions>) -> Operand {
.environment
.supports_arrow_function()
{
Operand::Value(Value::from("() => {}"))
// A minifier keeps the parameter, making this one byte shorter than the parameterless arrow.
Operand::Value(Value::from("x => {}"))
} else {
Operand::Value(Value::from("function() {}"))
}
Expand Down Expand Up @@ -935,6 +936,26 @@ impl ModuleCodeTemplate {
}
}

pub fn deferred_call(&mut self, function: &RuntimeGlobals, args: &str) -> String {
let needs_require_context = self.render_mode() == RuntimeGlobalsRenderMode::RspackExport
&& *function == RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT;
let function = self.render_runtime_globals(function);
// Keep the require runtime requirement even when the arrow form does not
// spell it out. Some runtime helpers receive it through their call context.
let require = self.render_runtime_globals(&RuntimeGlobals::REQUIRE);
if needs_require_context
|| !self
.compiler_options
.output
.environment
.supports_arrow_function()
{
format!("{function}.bind({require}, {args})")
} else {
self.returning_function(&format!("{function}({args})"), "")
}
}

pub fn basic_function(&self, args: &str, body: &str) -> String {
if self
.compiler_options
Expand Down Expand Up @@ -1665,13 +1686,11 @@ impl ModuleCodeTemplate {
)
);
}
write!(
appending,
".then({}.bind({}, {module_id_expr}, {mode}))",
self.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT),
self.render_runtime_globals(&RuntimeGlobals::REQUIRE)
)
.expect("infallible write to String");
let deferred_call = self.deferred_call(
&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT,
&format!("{module_id_expr}, {mode}"),
);
write!(appending, ".then({deferred_call})").expect("infallible write to String");
} else if let Some(header) = header {
let rendered_async_deps_fn =
self.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT);
Expand All @@ -1687,9 +1706,11 @@ impl ModuleCodeTemplate {
);
} else {
appending = format!(
".then({}.bind({}, {module_id_expr}, {mode}))",
self.render_runtime_globals(&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT),
self.render_runtime_globals(&RuntimeGlobals::REQUIRE)
".then({})",
self.deferred_call(
&RuntimeGlobals::MAKE_DEFERRED_NAMESPACE_OBJECT,
&format!("{module_id_expr}, {mode}"),
)
);
}
return format!("{promise}{appending}");
Expand All @@ -1705,9 +1726,8 @@ impl ModuleCodeTemplate {
)
} else {
appending = format!(
".then({}.bind({}, {module_id_expr}))",
self.render_runtime_globals(&RuntimeGlobals::REQUIRE),
self.render_runtime_globals(&RuntimeGlobals::REQUIRE),
".then({})",
self.deferred_call(&RuntimeGlobals::REQUIRE, &module_id_expr)
);
}
}
Expand Down Expand Up @@ -1738,9 +1758,8 @@ return {}
)
} else {
appending = format!(
".then({}.bind({}, {module_id_expr}))",
self.render_runtime_globals(&RuntimeGlobals::REQUIRE),
self.render_runtime_globals(&RuntimeGlobals::REQUIRE),
".then({})",
self.deferred_call(&RuntimeGlobals::REQUIRE, &module_id_expr)
);
}
appending.push_str(
Expand Down Expand Up @@ -1772,9 +1791,11 @@ return {}
);
} else {
appending = format!(
".then({}.bind({}, {module_id_expr}, {fake_type}))",
self.render_runtime_globals(&RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT),
self.render_runtime_globals(&RuntimeGlobals::REQUIRE),
".then({})",
self.deferred_call(
&RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT,
&format!("{module_id_expr}, {fake_type}"),
)
);
}
}
Expand Down Expand Up @@ -1926,6 +1947,21 @@ impl RuntimeCodeTemplate {
)
}
}

/// Renders a self-defaulting assignment with the shortest supported syntax.
pub fn assign_or(&self, target: &str, value: &str) -> String {
if self
.compiler_options
.output
.environment
.supports_logical_assignment()
{
format!("{target} ||= {value}")
} else {
format!("{target} = {target} || {value}")
}
}

pub fn render(&self, key: &str, params: Option<serde_json::Value>) -> Result<String, Error> {
let mut render_params = Value::Object(Default::default());

Expand Down
18 changes: 9 additions & 9 deletions crates/rspack_plugin_esm_library/src/dependency/dyn_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ fn then_expr(
match exports_type {
ExportsType::Namespace => {
appending = format!(
".then({}.bind({}, {module_id_expr}))",
runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
".then({})",
runtime_template.deferred_call(&RuntimeGlobals::REQUIRE, &module_id_expr),
);
}
_ => {
Expand All @@ -70,9 +69,8 @@ fn then_expr(
.expect("should have module"),
) {
appending = format!(
".then({}.bind({}, {module_id_expr}))",
runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE)
".then({})",
runtime_template.deferred_call(&RuntimeGlobals::REQUIRE, &module_id_expr),
);
appending.push_str(
format!(
Expand All @@ -86,9 +84,11 @@ fn then_expr(
} else {
fake_type |= FakeNamespaceObjectMode::MODULE_ID;
appending = format!(
".then({}.bind({}, {module_id_expr}, {fake_type}))",
runtime_template.render_runtime_globals(&RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT),
runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE)
".then({})",
runtime_template.deferred_call(
&RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT,
&format!("{module_id_expr}, {fake_type}"),
)
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use rspack_core::{Compilation, ModuleIdentifier, RuntimeSpec};

/// A classic worker entry reads top-level `this` as its global scope. Only
/// return true when this module is an entry module and every matching entry
/// role is a classic worker, so imported modules, normal entries, and module
/// worklets keep exports semantics.
pub(super) fn is_worker_entry_this(
compilation: &Compilation,
module: ModuleIdentifier,
runtime: Option<&RuntimeSpec>,
) -> bool {
let Some(runtime) = runtime.filter(|runtime| runtime.len() == 1) else {
return false;
};
if compilation.options.output.module {
return false;
}

let artifact = &compilation.build_chunk_graph_artifact;
let mut worker_entry = false;
for chunk_ukey in artifact.chunk_graph.get_module_chunks(module) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict the rewrite to actual worker entry modules

When a classic worker imports another CommonJS module that uses top-level this (for example, this.value = 1), that dependency is also returned by get_module_chunks and shares the worker's single runtime, so this function rewrites its this to the worker global as well. Only the worker entry module should receive script-level global semantics; imported modules still execute as CommonJS module factories where top-level this aliases their exports. Check that module is among the chunk's entry modules (and use its associated entrypoint) rather than treating every module placed in a worker chunk as an entry.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7052b2f. is_worker_entry_this now requires the module to be present in the chunk entry-module map and reads that entry module associated entrypoint. Imported CommonJS modules therefore retain exports semantics. I also added a normal/RuntimeMode regression where a classic worker imports a CommonJS dependency using top-level this.

let chunk = artifact.chunk_by_ukey.expect_get(chunk_ukey);
if chunk.runtime() != runtime {
continue;
}
let Some(group_ukey) = artifact
.chunk_graph
.get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey)
.get(&module)
else {
continue;
};
let group = artifact.chunk_group_by_ukey.expect_get(group_ukey);
let Some(options) = group.kind.get_entry_options() else {
return false;
};
if options.worker != Some(true) || options.worklet == Some(true) {
return false;
}
worker_entry = true;
}
worker_entry
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use rspack_core::{
DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate,
DependencyTemplateType, DependencyType, ExportNameOrSpec, ExportSpec, ExportsInfoArtifact,
ExportsOfExportsSpec, ExportsSpec, InitFragmentExt, InitFragmentKey, InitFragmentStage,
ModuleGraph, ModuleGraphCacheArtifact, NormalInitFragment, TemplateContext,
TemplateReplaceSource, UsedName, property_access,
ModuleGraph, ModuleGraphCacheArtifact, NormalInitFragment, RuntimeGlobals, RuntimeSpec,
TemplateContext, TemplateReplaceSource, UsedName, property_access,
};
use rspack_hash::{RspackHash, RspackHasher};
use rspack_util::json_stringify_str;
use swc_atoms::Atom;

use super::common_js_dependency_helpers::is_worker_entry_this;
use crate::dependency::commonjs::OBJECT_PROTOTYPE_METHODS;

#[cacheable]
Expand Down Expand Up @@ -133,6 +135,26 @@ impl DependencyCodeGeneration for CommonJsExportsDependency {
fn dependency_template(&self) -> Option<DependencyTemplateType> {
Some(CommonJsExportsDependencyTemplate::template_type())
}

fn update_hash(
&self,
hasher: &mut RspackHasher,
compilation: &rspack_core::Compilation,
runtime: Option<&RuntimeSpec>,
) {
if !self.base.is_this() {
return;
}
let module_graph = compilation.get_module_graph();
let worker_global = module_graph
.get_parent_module(&self.id)
.is_some_and(|module| is_worker_entry_this(compilation, *module, runtime));
if worker_global {
"worker global".hash(hasher);
} else {
"exports".hash(hasher);
}
}
}

impl AsContextDependency for CommonJsExportsDependency {}
Expand Down Expand Up @@ -175,10 +197,17 @@ impl DependencyTemplate for CommonJsExportsDependencyTemplate {
.module_by_identifier(&module.identifier())
.expect("should have mgm");

let is_worker_entry =
dep.base.is_this() && is_worker_entry_this(compilation, module.identifier(), *runtime);

let exports_info = compilation
.exports_info_artifact
.get_exports_info_data(&module.identifier());
let used = exports_info.get_used_name(&compilation.exports_info_artifact, *runtime, &dep.names);
let used = if is_worker_entry {
Some(UsedName::Normal(dep.names.clone()))
} else {
exports_info.get_used_name(&compilation.exports_info_artifact, *runtime, &dep.names)
};

let exports_argument = module.get_exports_argument();
let module_argument = module.get_module_argument();
Expand All @@ -190,6 +219,8 @@ impl DependencyTemplate for CommonJsExportsDependencyTemplate {
"{}.exports",
runtime_template.render_module_argument(module_argument)
)
} else if is_worker_entry {
runtime_template.render_runtime_globals(&RuntimeGlobals::GLOBAL)
} else if dep.base.is_this() {
runtime_template.render_this_exports()
} else {
Expand Down
Loading
Loading