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
26 changes: 26 additions & 0 deletions test/e2e/app-dir/worker/app/shared-bundled/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'
import { useState } from 'react'

export default function SharedWorkerBundledPage() {
const [state, setState] = useState('default')
return (
<div>
<button
onClick={() => {
// This should be bundled by turbopack since it's a relative import
const worker = new SharedWorker(
new URL('../shared-worker', import.meta.url)
)
worker.port.addEventListener('message', (event) => {
setState(event.data)
})
worker.port.start()
}}
>
Get shared worker data (bundled)
</button>
<p>SharedWorker bundled state: </p>
<p id="shared-worker-bundled-state">{state}</p>
</div>
)
}
23 changes: 23 additions & 0 deletions test/e2e/app-dir/worker/app/shared-unbundled/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'
import { useState } from 'react'

export default function SharedWorkerPage() {
const [state, setState] = useState('default')
return (
<div>
<button
onClick={() => {
const worker = new SharedWorker('/unbundled-shared-worker.js')
worker.port.addEventListener('message', (event) => {
setState(event.data)
})
worker.port.start()
}}
>
Get shared worker data
</button>
<p>SharedWorker state: </p>
<p id="shared-worker-state">{state}</p>
</div>
)
}
16 changes: 16 additions & 0 deletions test/e2e/app-dir/worker/app/shared-worker.ts
Original file line number Diff line number Diff line change
@@ -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()
})
6 changes: 6 additions & 0 deletions test/e2e/app-dir/worker/public/unbundled-shared-worker.js
Original file line number Diff line number Diff line change
@@ -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()
})
30 changes: 30 additions & 0 deletions test/e2e/app-dir/worker/worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
11 changes: 11 additions & 0 deletions turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -3623,6 +3627,7 @@ pub enum WellKnownFunctionKind {
NodeResolveFrom,
NodeProtobufLoad,
WorkerConstructor,
SharedWorkerConstructor,
URLConstructor,
}

Expand Down Expand Up @@ -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),
Expand Down
45 changes: 44 additions & 1 deletion turbopack/crates/turbopack-ecmascript/src/references/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -1719,6 +1719,43 @@ async fn handle_call<G: Fn(Vec<Effect>) + 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,
),
Expand Down Expand Up @@ -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),
Expand Down
16 changes: 12 additions & 4 deletions turbopack/crates/turbopack-ecmascript/src/references/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use crate::{
pub struct WorkerAssetReference {
pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
pub request: ResolvedVc<Request>,
pub worker_type: WorkerReferenceSubType,
pub issue_source: IssueSource,
pub in_try: bool,
}
Expand All @@ -40,12 +41,14 @@ impl WorkerAssetReference {
pub fn new(
origin: ResolvedVc<Box<dyn ResolveOrigin>>,
request: ResolvedVc<Request>,
worker_type: WorkerReferenceSubType,
issue_source: IssueSource,
in_try: bool,
) -> Self {
WorkerAssetReference {
origin,
request,
worker_type,
issue_source,
in_try,
}
Expand All @@ -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,
);
Expand All @@ -81,7 +83,7 @@ impl WorkerAssetReference {
return Ok(None);
};

Ok(Some(WorkerLoaderModule::new(*chunkable)))
Ok(Some(WorkerLoaderModule::new(*chunkable, self.worker_type)))
}
}

Expand All @@ -103,8 +105,14 @@ impl ModuleReference for WorkerAssetReference {
impl ValueToString for WorkerAssetReference {
#[turbo_tasks::function]
async fn to_string(&self) -> Result<Vc<RcStr>> {
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(),
))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,16 @@ impl WorkerLoaderChunkItem {
impl EcmascriptChunkItem for WorkerLoaderChunkItem {
#[turbo_tasks::function]
async fn content(self: Vc<Self>) -> Result<Vc<EcmascriptChunkItemContent>> {
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
.iter()
.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:#}));
Expand Down
12 changes: 10 additions & 2 deletions turbopack/crates/turbopack-ecmascript/src/worker_chunk/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use turbopack_core::{
module::Module,
module_graph::ModuleGraph,
reference::{ModuleReference, ModuleReferences},
reference_type::WorkerReferenceSubType,
resolve::ModuleResolveResult,
};

Expand All @@ -21,13 +22,20 @@ use super::chunk_item::WorkerLoaderChunkItem;
#[turbo_tasks::value]
pub struct WorkerLoaderModule {
pub inner: ResolvedVc<Box<dyn ChunkableModule>>,
pub worker_type: WorkerReferenceSubType,
}

#[turbo_tasks::value_impl]
impl WorkerLoaderModule {
#[turbo_tasks::function]
pub fn new(module: ResolvedVc<Box<dyn ChunkableModule>>) -> Vc<Self> {
Self::cell(WorkerLoaderModule { inner: module })
pub fn new(
module: ResolvedVc<Box<dyn ChunkableModule>>,
worker_type: WorkerReferenceSubType,
) -> Vc<Self> {
Self::cell(WorkerLoaderModule {
inner: module,
worker_type,
})
}

#[turbo_tasks::function]
Expand Down
Loading