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_code_gen_reference(
224 self,
225 path: AstPath,
226 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
227 let scope = self.scope.clone();
228 let reference = self.resolved_cell();
229 (
230 ResolvedVc::upcast(reference),
231 CodeGen::ServiceWorkerAssetReferenceCodeGen(ServiceWorkerAssetReferenceCodeGen {
232 scope,
233 path,
234 }),
235 )
236 }
237}
238
239#[derive(
240 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
241)]
242pub struct ServiceWorkerAssetReferenceCodeGen {
243 scope: RcStr,
244 path: AstPath,
245}
246
247impl ServiceWorkerAssetReferenceCodeGen {
248 pub async fn code_generation(
249 &self,
250 _chunking_context: Vc<Box<dyn ChunkingContext>>,
251 ) -> Result<CodeGeneration> {
252 let url = format!("/{}", service_worker_chunk_filename(&self.scope));
255
256 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
257 let message = if let Expr::Call(call_expr) = expr {
258 match call_expr.args.first_mut() {
259 Some(ExprOrSpread {
260 spread: None,
261 expr: url_expr,
262 }) => {
263 **url_expr = Expr::Lit(Lit::Str(url.as_str().into()));
264 return;
265 }
266 Some(ExprOrSpread {
267 spread: Some(_), ..
268 }) => "spread operator is illegal in navigator.serviceWorker.register().",
269 None => "navigator.serviceWorker.register() requires at least 1 argument",
270 }
271 } else {
272 "visitor must be executed on a CallExpr"
273 };
274 *expr = *quote_expr!(
275 "(() => { throw new Error($message); })()",
276 message: Expr = Expr::Lit(Lit::Str(message.into()))
277 );
278 });
279
280 Ok(CodeGeneration::visitors(vec![visitor]))
281 }
282}
283
284impl From<ServiceWorkerAssetReferenceCodeGen> for CodeGen {
285 fn from(val: ServiceWorkerAssetReferenceCodeGen) -> Self {
286 CodeGen::ServiceWorkerAssetReferenceCodeGen(val)
287 }
288}