turbopack_ecmascript/references/
service_worker.rs1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4 ecma::ast::{Expr, ExprOrSpread, Lit},
5 quote_expr,
6};
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::{
9 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10 turbofmt,
11};
12use turbo_tasks_hash::{encode_hex, hash_xxh3_hash64};
13use turbopack_core::{
14 chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext, ChunkingType},
15 ident::AssetIdent,
16 issue::IssueSource,
17 module::{Module, ModuleSideEffects},
18 module_graph::ModuleGraph,
19 reference::{ModuleReference, ModuleReferences},
20 reference_type::{ReferenceType, WorkerReferenceSubType},
21 resolve::{
22 ModuleResolveResult, ModuleResolveResultItem, ResolveErrorMode, origin::ResolveOrigin,
23 parse::Request, url_resolve,
24 },
25 source::OptionSource,
26};
27
28use crate::{
29 chunk::{
30 EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
31 ecmascript_chunk_item,
32 },
33 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
34 create_visitor,
35 references::AstPath,
36};
37
38pub fn service_worker_chunk_filename(scope: &str) -> RcStr {
45 let trimmed = scope.trim_matches('/');
46 if trimmed.is_empty() {
47 return rcstr!("sw.js");
48 }
49 let slug: String = trimmed
50 .chars()
51 .map(|c| match c {
52 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => c,
53 _ => '-',
54 })
55 .collect();
56 let hash = encode_hex(hash_xxh3_hash64(trimmed));
57 RcStr::from(format!("sw-{slug}-{hash}.js"))
58}
59
60#[turbo_tasks::value(shared)]
64pub struct ServiceWorkerEntryModule {
65 pub inner: ResolvedVc<Box<dyn Module>>,
66 pub scope: RcStr,
67}
68
69#[turbo_tasks::value_impl]
70impl Module for ServiceWorkerEntryModule {
71 #[turbo_tasks::function]
72 async fn ident(&self) -> Result<Vc<AssetIdent>> {
73 Ok(self
74 .inner
75 .ident()
76 .owned()
77 .await?
78 .with_modifier(format!("service worker entry [{}]", self.scope).into())
79 .into_vc())
80 }
81
82 #[turbo_tasks::function]
83 fn source(&self) -> Vc<OptionSource> {
84 Vc::cell(None)
85 }
86
87 #[turbo_tasks::function]
88 fn references(&self) -> Vc<ModuleReferences> {
89 Vc::cell(vec![])
90 }
91
92 #[turbo_tasks::function]
93 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
94 ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
95 }
96}
97
98#[turbo_tasks::value_impl]
99impl ChunkableModule for ServiceWorkerEntryModule {
100 #[turbo_tasks::function]
101 fn as_chunk_item(
102 self: ResolvedVc<Self>,
103 module_graph: ResolvedVc<ModuleGraph>,
104 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
105 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
106 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
107 }
108}
109
110#[turbo_tasks::value_impl]
111impl EcmascriptChunkPlaceable for ServiceWorkerEntryModule {
112 #[turbo_tasks::function]
113 fn get_exports(&self) -> Vc<EcmascriptExports> {
114 EcmascriptExports::None.cell()
115 }
116
117 #[turbo_tasks::function]
118 fn chunk_item_content(
119 &self,
120 _chunking_context: Vc<Box<dyn ChunkingContext>>,
121 _module_graph: Vc<ModuleGraph>,
122 _async_module_info: Option<Vc<AsyncModuleInfo>>,
123 _estimated: bool,
124 ) -> Vc<EcmascriptChunkItemContent> {
125 EcmascriptChunkItemContent::default().cell()
127 }
128}
129
130#[turbo_tasks::value]
134#[derive(Hash, Debug)]
135pub struct ServiceWorkerAssetReference {
136 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
137 request: ResolvedVc<Request>,
138 scope: RcStr,
139 issue_source: IssueSource,
140 error_mode: ResolveErrorMode,
141}
142
143impl ServiceWorkerAssetReference {
144 pub fn new(
145 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
146 request: ResolvedVc<Request>,
147 scope: RcStr,
148 issue_source: IssueSource,
149 error_mode: ResolveErrorMode,
150 ) -> Self {
151 ServiceWorkerAssetReference {
152 origin,
153 request,
154 scope,
155 issue_source,
156 error_mode,
157 }
158 }
159}
160
161#[turbo_tasks::value_impl]
162impl ModuleReference for ServiceWorkerAssetReference {
163 #[turbo_tasks::function]
164 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
165 let result = url_resolve(
166 *self.origin,
167 *self.request,
168 ReferenceType::Worker(WorkerReferenceSubType::ServiceWorker),
169 Some(self.issue_source),
170 self.error_mode,
171 );
172
173 let result_ref = result.await?;
174 let mut primary = Vec::with_capacity(result_ref.primary.len());
175 for (request_key, item) in result_ref.primary.iter() {
176 match item {
177 ModuleResolveResultItem::Module(module) => {
178 let marker = ServiceWorkerEntryModule {
179 inner: *module,
180 scope: self.scope.clone(),
181 }
182 .resolved_cell();
183 primary.push((
184 request_key.clone(),
185 ModuleResolveResultItem::Module(ResolvedVc::upcast(marker)),
186 ));
187 }
188 _ => primary.push((request_key.clone(), item.clone())),
189 }
190 }
191
192 Ok(ModuleResolveResult {
193 primary: primary.into_boxed_slice(),
194 affecting_sources: result_ref.affecting_sources.clone(),
195 }
196 .cell())
197 }
198
199 fn chunking_type(&self) -> Option<ChunkingType> {
200 Some(ChunkingType::Parallel {
203 inherit_async: false,
204 hoisted: false,
205 })
206 }
207
208 fn source(&self) -> Option<IssueSource> {
209 Some(self.issue_source)
210 }
211}
212
213#[turbo_tasks::value_impl]
214impl ValueToString for ServiceWorkerAssetReference {
215 #[turbo_tasks::function]
216 async fn to_string(&self) -> Result<Vc<RcStr>> {
217 let request = self.request.to_string();
218 Ok(Vc::cell(turbofmt!("service worker {request}").await?))
219 }
220}
221
222impl IntoCodeGenReference for ServiceWorkerAssetReference {
223 fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
224 ResolvedVc::upcast(self.resolved_cell())
225 }
226
227 fn into_code_gen_reference(
228 self,
229 path: AstPath,
230 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
231 let scope = self.scope.clone();
232 let reference = self.resolved_cell();
233 (
234 ResolvedVc::upcast(reference),
235 CodeGen::ServiceWorkerAssetReferenceCodeGen(ServiceWorkerAssetReferenceCodeGen {
236 scope,
237 path,
238 }),
239 )
240 }
241}
242
243#[derive(
244 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
245)]
246pub struct ServiceWorkerAssetReferenceCodeGen {
247 scope: RcStr,
248 path: AstPath,
249}
250
251impl ServiceWorkerAssetReferenceCodeGen {
252 pub async fn code_generation(
253 &self,
254 chunking_context: Vc<Box<dyn ChunkingContext>>,
255 ) -> Result<CodeGeneration> {
256 let base_path = chunking_context.service_worker_scope_base_path().await?;
266 let base_path = base_path.trim_end_matches('/');
267 let url = format!(
268 "{base_path}/_next/static/service-worker/{}",
269 service_worker_chunk_filename(&self.scope)
270 );
271 let scope = match self.scope.as_str() {
272 "/" if base_path.is_empty() => "/".to_string(),
273 "/" => base_path.to_string(),
274 s => format!("{base_path}{s}"),
275 };
276
277 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
278 let message = if let Expr::Call(call_expr) = expr {
279 match call_expr.args.first() {
280 Some(ExprOrSpread { spread: None, .. }) => {
281 let scope_expr = Expr::Lit(Lit::Str(scope.as_str().into()));
282 let options = match call_expr.args.get(1) {
283 Some(ExprOrSpread { spread: None, expr }) => quote_expr!(
284 "({ ...$user, scope: $scope })",
285 user: Expr = (**expr).clone(),
286 scope: Expr = scope_expr
287 ),
288 _ => quote_expr!(
289 "({ scope: $scope })",
290 scope: Expr = scope_expr
291 ),
292 };
293 call_expr.args.truncate(1);
294 call_expr.args[0] = ExprOrSpread {
295 spread: None,
296 expr: Box::new(Expr::Lit(Lit::Str(url.as_str().into()))),
297 };
298 call_expr.args.push(ExprOrSpread {
299 spread: None,
300 expr: options,
301 });
302 return;
303 }
304 Some(ExprOrSpread {
305 spread: Some(_), ..
306 }) => "spread operator is illegal in navigator.serviceWorker.register().",
307 None => "navigator.serviceWorker.register() requires at least 1 argument",
308 }
309 } else {
310 "visitor must be executed on a CallExpr"
311 };
312 *expr = *quote_expr!(
313 "(() => { throw new Error($message); })()",
314 message: Expr = Expr::Lit(Lit::Str(message.into()))
315 );
316 });
317
318 Ok(CodeGeneration::visitors(vec![visitor]))
319 }
320}
321
322impl From<ServiceWorkerAssetReferenceCodeGen> for CodeGen {
323 fn from(val: ServiceWorkerAssetReferenceCodeGen) -> Self {
324 CodeGen::ServiceWorkerAssetReferenceCodeGen(val)
325 }
326}