diff --git a/test/e2e/app-dir/worker/app/shared-bundled/page.js b/test/e2e/app-dir/worker/app/shared-bundled/page.js
new file mode 100644
index 00000000000000..c328f4c10bd413
--- /dev/null
+++ b/test/e2e/app-dir/worker/app/shared-bundled/page.js
@@ -0,0 +1,26 @@
+'use client'
+import { useState } from 'react'
+
+export default function SharedWorkerBundledPage() {
+ const [state, setState] = useState('default')
+ return (
+
+
+
SharedWorker bundled state:
+
{state}
+
+ )
+}
diff --git a/test/e2e/app-dir/worker/app/shared-unbundled/page.js b/test/e2e/app-dir/worker/app/shared-unbundled/page.js
new file mode 100644
index 00000000000000..46037438949230
--- /dev/null
+++ b/test/e2e/app-dir/worker/app/shared-unbundled/page.js
@@ -0,0 +1,23 @@
+'use client'
+import { useState } from 'react'
+
+export default function SharedWorkerPage() {
+ const [state, setState] = useState('default')
+ return (
+
+
+
SharedWorker state:
+
{state}
+
+ )
+}
diff --git a/test/e2e/app-dir/worker/app/shared-worker.ts b/test/e2e/app-dir/worker/app/shared-worker.ts
new file mode 100644
index 00000000000000..b77dfdac01cf35
--- /dev/null
+++ b/test/e2e/app-dir/worker/app/shared-worker.ts
@@ -0,0 +1,16 @@
+// SharedWorker script - handles multiple connections
+self.addEventListener('connect', (event: MessageEvent) => {
+ const port = event.ports[0]
+
+ // Import the dependency and send the message
+ import('./worker-dep')
+ .then((mod) => {
+ port.postMessage('shared-worker.ts:' + mod.default)
+ })
+ .catch((error) => {
+ console.error('SharedWorker import error:', error)
+ port.postMessage('error: ' + error.message)
+ })
+
+ port.start()
+})
diff --git a/test/e2e/app-dir/worker/public/unbundled-shared-worker.js b/test/e2e/app-dir/worker/public/unbundled-shared-worker.js
new file mode 100644
index 00000000000000..606fb5d041d3f3
--- /dev/null
+++ b/test/e2e/app-dir/worker/public/unbundled-shared-worker.js
@@ -0,0 +1,6 @@
+// SharedWorker script for unbundled test
+self.addEventListener('connect', (event) => {
+ const port = event.ports[0]
+ port.postMessage('unbundled-shared-worker')
+ port.start()
+})
diff --git a/test/e2e/app-dir/worker/worker.test.ts b/test/e2e/app-dir/worker/worker.test.ts
index aa4bd25edf7d13..0ee39c8e6eb0ff 100644
--- a/test/e2e/app-dir/worker/worker.test.ts
+++ b/test/e2e/app-dir/worker/worker.test.ts
@@ -49,4 +49,34 @@ describe('app dir - workers', () => {
)
)
})
+
+ it('should support shared workers with string specifiers', async () => {
+ const browser = await next.browser('/shared-unbundled')
+ expect(await browser.elementByCss('#shared-worker-state').text()).toBe(
+ 'default'
+ )
+
+ await browser.elementByCss('button').click()
+
+ await retry(async () =>
+ expect(await browser.elementByCss('#shared-worker-state').text()).toBe(
+ 'unbundled-shared-worker'
+ )
+ )
+ })
+
+ it('should support shared workers with dynamic imports', async () => {
+ const browser = await next.browser('/shared-bundled')
+ expect(
+ await browser.elementByCss('#shared-worker-bundled-state').text()
+ ).toBe('default')
+
+ await browser.elementByCss('button').click()
+
+ await retry(async () =>
+ expect(
+ await browser.elementByCss('#shared-worker-bundled-state').text()
+ ).toBe('shared-worker.ts:worker-dep')
+ )
+ })
})
diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/imports.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/imports.rs
index 11dc6d364b2723..880e78496a54ad 100644
--- a/turbopack/crates/turbopack-ecmascript/src/analyzer/imports.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/imports.rs
@@ -770,7 +770,9 @@ impl Visit for Analyzer<'_> {
// we could actually unwrap thanks to the optimisation above but it can't hurt to be safe...
if let Some(comments) = self.comments {
let callee_span = match &n.callee {
- box Expr::Ident(Ident { sym, .. }) if sym == "Worker" => Some(n.span),
+ box Expr::Ident(Ident { sym, .. }) if sym == "Worker" || sym == "SharedWorker" => {
+ Some(n.span)
+ }
_ => None,
};
diff --git a/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs b/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs
index 6b2334dabea335..fe806cc043be17 100644
--- a/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs
@@ -1958,6 +1958,10 @@ impl JsValue {
"Worker".to_string(),
"The standard Worker constructor: https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker"
),
+ WellKnownFunctionKind::SharedWorkerConstructor => (
+ "SharedWorker".to_string(),
+ "The standard SharedWorker constructor: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker/SharedWorker"
+ ),
WellKnownFunctionKind::URLConstructor => (
"URL".to_string(),
"The standard URL constructor: https://developer.mozilla.org/en-US/docs/Web/API/URL/URL"
@@ -3623,6 +3627,7 @@ pub enum WellKnownFunctionKind {
NodeResolveFrom,
NodeProtobufLoad,
WorkerConstructor,
+ SharedWorkerConstructor,
URLConstructor,
}
@@ -3795,6 +3800,12 @@ pub mod test_utils {
true,
"ignored Worker constructor",
),
+ "SharedWorker" => JsValue::unknown_if(
+ ignore,
+ JsValue::WellKnownFunction(WellKnownFunctionKind::SharedWorkerConstructor),
+ true,
+ "ignored SharedWorker constructor",
+ ),
"define" => JsValue::WellKnownFunction(WellKnownFunctionKind::Define),
"URL" => JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
"process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcess),
diff --git a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs
index 064887b42c128b..90daf597f8b950 100644
--- a/turbopack/crates/turbopack-ecmascript/src/references/mod.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/references/mod.rs
@@ -75,7 +75,7 @@ use turbopack_core::{
issue::{IssueExt, IssueSeverity, IssueSource, StyledString, analyze::AnalyzeIssue},
module::Module,
reference::{ModuleReference, ModuleReferences},
- reference_type::{CommonJsReferenceSubType, ReferenceType},
+ reference_type::{CommonJsReferenceSubType, ReferenceType, WorkerReferenceSubType},
resolve::{
FindContextFileResult, ModulePart, find_context_file,
origin::{PlainResolveOrigin, ResolveOrigin, ResolveOriginExt},
@@ -1719,6 +1719,43 @@ async fn handle_call) + Send + Sync>(
WorkerAssetReference::new(
origin,
Request::parse(pat).to_resolved().await?,
+ WorkerReferenceSubType::WebWorker,
+ issue_source(source, span),
+ in_try,
+ ),
+ ast_path.to_vec().into(),
+ );
+ }
+
+ return Ok(());
+ }
+ // Ignore (e.g. dynamic parameter or string literal), just as Webpack does
+ return Ok(());
+ }
+ JsValue::WellKnownFunction(WellKnownFunctionKind::SharedWorkerConstructor) => {
+ let args = linked_args(args).await?;
+ if let Some(url @ JsValue::Url(_, JsValueUrlKind::Relative)) = args.first() {
+ let pat = js_value_to_pattern(url);
+ if !pat.has_constant_parts() {
+ let (args, hints) = explain_args(&args);
+ handler.span_warn_with_code(
+ span,
+ &format!("new SharedWorker({args}) is very dynamic{hints}",),
+ DiagnosticId::Lint(
+ errors::failed_to_analyze::ecmascript::NEW_WORKER.to_string(),
+ ),
+ );
+ if ignore_dynamic_requests {
+ return Ok(());
+ }
+ }
+
+ if *compile_time_info.environment().rendering().await? == Rendering::Client {
+ analysis.add_reference_code_gen(
+ WorkerAssetReference::new(
+ origin,
+ Request::parse(pat).to_resolved().await?,
+ WorkerReferenceSubType::SharedWorker,
issue_source(source, span),
in_try,
),
@@ -3149,6 +3186,12 @@ async fn value_visitor_inner(
true,
"ignored Worker constructor",
),
+ "SharedWorker" => JsValue::unknown_if(
+ ignore,
+ JsValue::WellKnownFunction(WellKnownFunctionKind::SharedWorkerConstructor),
+ true,
+ "ignored SharedWorker constructor",
+ ),
"define" => JsValue::WellKnownFunction(WellKnownFunctionKind::Define),
"URL" => JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
"process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcess),
diff --git a/turbopack/crates/turbopack-ecmascript/src/references/worker.rs b/turbopack/crates/turbopack-ecmascript/src/references/worker.rs
index 17e7b474fd6ed3..cc41a0905d381d 100644
--- a/turbopack/crates/turbopack-ecmascript/src/references/worker.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/references/worker.rs
@@ -32,6 +32,7 @@ use crate::{
pub struct WorkerAssetReference {
pub origin: ResolvedVc>,
pub request: ResolvedVc,
+ pub worker_type: WorkerReferenceSubType,
pub issue_source: IssueSource,
pub in_try: bool,
}
@@ -40,12 +41,14 @@ impl WorkerAssetReference {
pub fn new(
origin: ResolvedVc>,
request: ResolvedVc,
+ worker_type: WorkerReferenceSubType,
issue_source: IssueSource,
in_try: bool,
) -> Self {
WorkerAssetReference {
origin,
request,
+ worker_type,
issue_source,
in_try,
}
@@ -59,8 +62,7 @@ impl WorkerAssetReference {
let module = url_resolve(
*self.origin,
*self.request,
- // TODO support more worker types
- ReferenceType::Worker(WorkerReferenceSubType::WebWorker),
+ ReferenceType::Worker(self.worker_type),
Some(self.issue_source),
self.in_try,
);
@@ -81,7 +83,7 @@ impl WorkerAssetReference {
return Ok(None);
};
- Ok(Some(WorkerLoaderModule::new(*chunkable)))
+ Ok(Some(WorkerLoaderModule::new(*chunkable, self.worker_type)))
}
}
@@ -103,8 +105,14 @@ impl ModuleReference for WorkerAssetReference {
impl ValueToString for WorkerAssetReference {
#[turbo_tasks::function]
async fn to_string(&self) -> Result> {
+ let worker_name = match self.worker_type {
+ WorkerReferenceSubType::WebWorker => "Worker",
+ WorkerReferenceSubType::SharedWorker => "SharedWorker",
+ WorkerReferenceSubType::ServiceWorker => "ServiceWorker",
+ _ => "Worker",
+ };
Ok(Vc::cell(
- format!("new Worker {}", self.request.to_string().await?,).into(),
+ format!("new {} {}", worker_name, self.request.to_string().await?,).into(),
))
}
}
diff --git a/turbopack/crates/turbopack-ecmascript/src/worker_chunk/chunk_item.rs b/turbopack/crates/turbopack-ecmascript/src/worker_chunk/chunk_item.rs
index 919e1e336e46d0..2f9933295f0968 100644
--- a/turbopack/crates/turbopack-ecmascript/src/worker_chunk/chunk_item.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/worker_chunk/chunk_item.rs
@@ -58,6 +58,7 @@ impl WorkerLoaderChunkItem {
impl EcmascriptChunkItem for WorkerLoaderChunkItem {
#[turbo_tasks::function]
async fn content(self: Vc) -> Result> {
+ let _this = self.await?;
let chunks_data = self.chunks_data().await?;
let chunks_data = chunks_data.iter().try_join().await?;
let chunks_data: Vec<_> = chunks_data
@@ -65,6 +66,8 @@ impl EcmascriptChunkItem for WorkerLoaderChunkItem {
.map(|chunk_data| EcmascriptChunkData::new(chunk_data))
.collect();
+ // All worker types use the same blob URL generation
+ // The difference is handled at the JavaScript level when creating the worker
let code = formatdoc! {
r#"
{TURBOPACK_EXPORT_VALUE}({TURBOPACK_WORKER_BLOB_URL}({chunks:#}));
diff --git a/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs b/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs
index 1e9072b9a0c2f4..3317301e1d9c18 100644
--- a/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs
@@ -11,6 +11,7 @@ use turbopack_core::{
module::Module,
module_graph::ModuleGraph,
reference::{ModuleReference, ModuleReferences},
+ reference_type::WorkerReferenceSubType,
resolve::ModuleResolveResult,
};
@@ -21,13 +22,20 @@ use super::chunk_item::WorkerLoaderChunkItem;
#[turbo_tasks::value]
pub struct WorkerLoaderModule {
pub inner: ResolvedVc>,
+ pub worker_type: WorkerReferenceSubType,
}
#[turbo_tasks::value_impl]
impl WorkerLoaderModule {
#[turbo_tasks::function]
- pub fn new(module: ResolvedVc>) -> Vc {
- Self::cell(WorkerLoaderModule { inner: module })
+ pub fn new(
+ module: ResolvedVc>,
+ worker_type: WorkerReferenceSubType,
+ ) -> Vc {
+ Self::cell(WorkerLoaderModule {
+ inner: module,
+ worker_type,
+ })
}
#[turbo_tasks::function]