Skip to main content

turbopack_browser/
chunking_context.rs

1use anyhow::{Context, Result, bail};
2use tracing::Instrument;
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{FxIndexMap, ResolvedVc, TaskInput, TryJoinIterExt, Upcast, ValueToString, Vc};
5use turbo_tasks_fs::FileSystemPath;
6use turbo_tasks_hash::HashAlgorithm;
7use turbopack_core::{
8    asset::{Asset, AssetContent},
9    chunk::{
10        AssetSuffix, Chunk, ChunkGroupResult, ChunkItem, ChunkType, ChunkableModule,
11        ChunkingConfig, ChunkingConfigs, ChunkingContext, ContentHashing, CrossOrigin,
12        EntryChunkGroupResult, EvaluatableAsset, EvaluatableAssets, MinifyType,
13        SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
14        availability_info::AvailabilityInfo,
15        chunk_group::{MakeChunkGroupResult, make_chunk_group},
16        chunk_id_strategy::ModuleIdStrategy,
17    },
18    environment::Environment,
19    ident::AssetIdent,
20    module::Module,
21    module_graph::{
22        ModuleGraph,
23        binding_usage_info::{BindingUsageInfo, ModuleExportUsage},
24        chunk_group_info::ChunkGroup,
25    },
26    output::{OutputAsset, OutputAssets},
27};
28use turbopack_ecmascript::{
29    async_chunk::module::AsyncLoaderModule,
30    chunk::EcmascriptChunk,
31    manifest::{chunk_asset::ManifestAsyncModule, loader_module::ManifestLoaderModule},
32};
33use turbopack_ecmascript_runtime::RuntimeType;
34
35use crate::ecmascript::{
36    chunk::EcmascriptBrowserChunk,
37    evaluate::chunk::EcmascriptBrowserEvaluateChunk,
38    list::asset::{EcmascriptDevChunkList, EcmascriptDevChunkListSource},
39    worker::EcmascriptBrowserWorkerEntrypoint,
40};
41
42#[turbo_tasks::value]
43#[derive(Debug, Clone, Copy, Hash, TaskInput)]
44pub enum CurrentChunkMethod {
45    StringLiteral,
46    DocumentCurrentScript,
47}
48
49pub const CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR: &str =
50    "typeof document === \"object\" ? document.currentScript : undefined";
51
52pub struct BrowserChunkingContextBuilder {
53    chunking_context: BrowserChunkingContext,
54}
55
56impl BrowserChunkingContextBuilder {
57    pub fn name(mut self, name: RcStr) -> Self {
58        self.chunking_context.name = Some(name);
59        self
60    }
61
62    pub fn hot_module_replacement(mut self) -> Self {
63        self.chunking_context.enable_hot_module_replacement = true;
64        self
65    }
66
67    pub fn source_map_source_type(mut self, source_map_source_type: SourceMapSourceType) -> Self {
68        self.chunking_context.source_map_source_type = source_map_source_type;
69        self
70    }
71
72    pub fn tracing(mut self, enable_tracing: bool) -> Self {
73        self.chunking_context.enable_tracing = enable_tracing;
74        self
75    }
76
77    pub fn nested_async_availability(mut self, enable_nested_async_availability: bool) -> Self {
78        self.chunking_context.enable_nested_async_availability = enable_nested_async_availability;
79        self
80    }
81
82    pub fn module_merging(mut self, enable_module_merging: bool) -> Self {
83        self.chunking_context.enable_module_merging = enable_module_merging;
84        self
85    }
86
87    pub fn dynamic_chunk_content_loading(
88        mut self,
89        enable_dynamic_chunk_content_loading: bool,
90    ) -> Self {
91        self.chunking_context.enable_dynamic_chunk_content_loading =
92            enable_dynamic_chunk_content_loading;
93        self
94    }
95
96    pub fn asset_base_path(mut self, asset_base_path: Option<RcStr>) -> Self {
97        self.chunking_context.asset_base_path = asset_base_path;
98        self
99    }
100
101    pub fn chunk_base_path(mut self, chunk_base_path: Option<RcStr>) -> Self {
102        self.chunking_context.chunk_base_path = chunk_base_path;
103        self
104    }
105
106    pub fn worker_asset_prefix(mut self, worker_asset_prefix: Option<RcStr>) -> Self {
107        self.chunking_context.worker_asset_prefix = worker_asset_prefix;
108        self
109    }
110
111    pub fn asset_suffix(mut self, asset_suffix: ResolvedVc<AssetSuffix>) -> Self {
112        self.chunking_context.asset_suffix = Some(asset_suffix);
113        self
114    }
115
116    pub fn runtime_type(mut self, runtime_type: RuntimeType) -> Self {
117        self.chunking_context.runtime_type = runtime_type;
118        self
119    }
120
121    pub fn manifest_chunks(mut self, manifest_chunks: bool) -> Self {
122        self.chunking_context.manifest_chunks = manifest_chunks;
123        self
124    }
125
126    pub fn minify_type(mut self, minify_type: MinifyType) -> Self {
127        self.chunking_context.minify_type = minify_type;
128        self
129    }
130
131    pub fn source_maps(mut self, source_maps: SourceMapsType) -> Self {
132        self.chunking_context.source_maps_type = source_maps;
133        self
134    }
135
136    pub fn current_chunk_method(mut self, method: CurrentChunkMethod) -> Self {
137        self.chunking_context.current_chunk_method = method;
138        self
139    }
140
141    pub fn module_id_strategy(mut self, module_id_strategy: ResolvedVc<ModuleIdStrategy>) -> Self {
142        self.chunking_context.module_id_strategy = Some(module_id_strategy);
143        self
144    }
145
146    pub fn export_usage(mut self, export_usage: Option<ResolvedVc<BindingUsageInfo>>) -> Self {
147        self.chunking_context.export_usage = export_usage;
148        self
149    }
150
151    pub fn unused_references(mut self, unused_references: ResolvedVc<UnusedReferences>) -> Self {
152        self.chunking_context.unused_references = Some(unused_references);
153        self
154    }
155
156    pub fn debug_ids(mut self, debug_ids: bool) -> Self {
157        self.chunking_context.debug_ids = debug_ids;
158        self
159    }
160
161    pub fn should_use_absolute_url_references(
162        mut self,
163        should_use_absolute_url_references: bool,
164    ) -> Self {
165        self.chunking_context.should_use_absolute_url_references =
166            should_use_absolute_url_references;
167        self
168    }
169
170    pub fn asset_root_path_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
171        self.chunking_context.asset_root_paths.insert(tag, path);
172        self
173    }
174
175    pub fn client_roots_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
176        self.chunking_context.client_roots.insert(tag, path);
177        self
178    }
179
180    pub fn asset_base_path_override(mut self, tag: RcStr, path: RcStr) -> Self {
181        self.chunking_context.asset_base_paths.insert(tag, path);
182        self
183    }
184
185    pub fn url_behavior_override(mut self, tag: RcStr, behavior: UrlBehavior) -> Self {
186        self.chunking_context.url_behaviors.insert(tag, behavior);
187        self
188    }
189
190    pub fn default_url_behavior(mut self, behavior: UrlBehavior) -> Self {
191        self.chunking_context.default_url_behavior = Some(behavior);
192        self
193    }
194
195    pub fn chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
196    where
197        T: Upcast<Box<dyn ChunkType>>,
198    {
199        self.chunking_context
200            .chunking_configs
201            .push((ResolvedVc::upcast_non_strict(ty), chunking_config));
202        self
203    }
204
205    pub fn chunk_content_hashing(mut self, content_hashing: ContentHashing) -> Self {
206        self.chunking_context.chunk_content_hashing = Some(content_hashing);
207        self
208    }
209
210    pub fn asset_content_hashing(mut self, content_hashing: ContentHashing) -> Self {
211        self.chunking_context.asset_content_hashing = content_hashing;
212        self
213    }
214
215    pub fn worker_forwarded_globals(mut self, globals: Vec<RcStr>) -> Self {
216        self.chunking_context
217            .worker_forwarded_globals
218            .extend(globals);
219        self
220    }
221
222    pub fn chunk_loading_global(mut self, chunk_loading_global: RcStr) -> Self {
223        self.chunking_context.chunk_loading_global = Some(chunk_loading_global);
224        self
225    }
226
227    pub fn hash_salt(mut self, salt: ResolvedVc<RcStr>) -> Self {
228        self.chunking_context.hash_salt = salt;
229        self
230    }
231
232    pub fn cross_origin(mut self, cross_origin: CrossOrigin) -> Self {
233        self.chunking_context.cross_origin = cross_origin;
234        self
235    }
236
237    pub fn build(self) -> Vc<BrowserChunkingContext> {
238        BrowserChunkingContext::cell(self.chunking_context)
239    }
240}
241
242/// A chunking context for development mode.
243///
244/// It uses readable filenames and module ids to improve development.
245/// It also uses a chunking heuristic that is incremental and cacheable.
246/// It splits "node_modules" separately as these are less likely to change
247/// during development
248#[turbo_tasks::value]
249#[derive(Debug, Clone)]
250pub struct BrowserChunkingContext {
251    name: Option<RcStr>,
252    /// The root path of the project
253    root_path: FileSystemPath,
254    /// The strategy to use for generating source map source uris
255    source_map_source_type: SourceMapSourceType,
256    /// This path is used to compute the url to request chunks from
257    output_root: FileSystemPath,
258    /// The relative path from the output_root to the root_path.
259    output_root_to_root_path: RcStr,
260    /// This path is used to compute the url to request assets from
261    client_root: FileSystemPath,
262    /// This path is used to compute the url to request chunks or assets from
263    #[bincode(with = "turbo_bincode::indexmap")]
264    client_roots: FxIndexMap<RcStr, FileSystemPath>,
265    /// Chunks are placed at this path
266    chunk_root_path: FileSystemPath,
267    /// Static assets are placed at this path
268    asset_root_path: FileSystemPath,
269    /// Static assets are placed at this path
270    #[bincode(with = "turbo_bincode::indexmap")]
271    asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
272    /// Base path that will be prepended to all chunk URLs when loading them.
273    /// This path will not appear in chunk paths or chunk data.
274    chunk_base_path: Option<RcStr>,
275    /// Base path for Web Worker URLs (the entrypoint and the module chunks
276    /// loaded inside the worker). When `Some`, overrides `chunk_base_path`
277    /// for those URLs. Mirrors webpack's `output.workerPublicPath`. Primary
278    /// use case: keep Worker URLs same-origin when
279    /// `chunk_base_path`/`assetPrefix` points to a cross-origin CDN
280    /// (browsers reject cross-origin Worker construction, and the worker
281    /// bootstrap rejects cross-origin module chunks).
282    worker_asset_prefix: Option<RcStr>,
283    /// Suffix that will be appended to all chunk URLs when loading them.
284    /// This path will not appear in chunk paths or chunk data.
285    asset_suffix: Option<ResolvedVc<AssetSuffix>>,
286    /// URL prefix that will be prepended to all static asset URLs when loading
287    /// them.
288    asset_base_path: Option<RcStr>,
289    /// URL prefix that will be prepended to all static asset URLs when loading
290    /// them.
291    #[bincode(with = "turbo_bincode::indexmap")]
292    asset_base_paths: FxIndexMap<RcStr, RcStr>,
293    /// URL behavior overrides for different tags.
294    #[bincode(with = "turbo_bincode::indexmap")]
295    url_behaviors: FxIndexMap<RcStr, UrlBehavior>,
296    /// Default URL behavior when no tag-specific override is found.
297    default_url_behavior: Option<UrlBehavior>,
298    /// Enable HMR for this chunking
299    enable_hot_module_replacement: bool,
300    /// Enable tracing for this chunking
301    enable_tracing: bool,
302    /// Enable nested async availability for this chunking
303    enable_nested_async_availability: bool,
304    /// Enable module merging
305    enable_module_merging: bool,
306    /// Enable dynamic chunk content loading.
307    enable_dynamic_chunk_content_loading: bool,
308    /// Enable debug IDs for chunks and source maps.
309    debug_ids: bool,
310    /// The environment chunks will be evaluated in.
311    environment: ResolvedVc<Environment>,
312    /// The kind of runtime to include in the output.
313    runtime_type: RuntimeType,
314    /// Whether to minify resulting chunks
315    minify_type: MinifyType,
316    /// Whether content hashing is enabled for chunk filenames.
317    chunk_content_hashing: Option<ContentHashing>,
318    /// Content hashing for asset filenames.
319    asset_content_hashing: ContentHashing,
320    /// Whether to generate source maps
321    source_maps_type: SourceMapsType,
322    /// Method to use when figuring out the current chunk src
323    current_chunk_method: CurrentChunkMethod,
324    /// Whether to use manifest chunks for lazy compilation
325    manifest_chunks: bool,
326    /// The module id strategy to use
327    module_id_strategy: Option<ResolvedVc<ModuleIdStrategy>>,
328    /// The module export usage info, if available.
329    export_usage: Option<ResolvedVc<BindingUsageInfo>>,
330    /// Which references are unused and should be skipped (e.g. during codegen).
331    unused_references: Option<ResolvedVc<UnusedReferences>>,
332    /// The chunking configs
333    chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
334    /// Whether to use absolute URLs for static assets (e.g. in CSS: `url("/absolute/path")`)
335    should_use_absolute_url_references: bool,
336    /// Global variable names to forward to workers (e.g. NEXT_DEPLOYMENT_ID)
337    worker_forwarded_globals: Vec<RcStr>,
338    /// The global variable name used for chunk loading.
339    /// Default: "TURBOPACK"
340    chunk_loading_global: Option<RcStr>,
341    /// Salt mixed into chunk and asset content hashes. Empty string means no salt.
342    hash_salt: ResolvedVc<RcStr>,
343    /// The crossorigin mode for dynamically loaded chunks.
344    cross_origin: CrossOrigin,
345}
346
347impl BrowserChunkingContext {
348    pub fn builder(
349        root_path: FileSystemPath,
350        output_root: FileSystemPath,
351        output_root_to_root_path: RcStr,
352        client_root: FileSystemPath,
353        chunk_root_path: FileSystemPath,
354        asset_root_path: FileSystemPath,
355        environment: ResolvedVc<Environment>,
356        runtime_type: RuntimeType,
357    ) -> BrowserChunkingContextBuilder {
358        BrowserChunkingContextBuilder {
359            chunking_context: BrowserChunkingContext {
360                name: None,
361                root_path,
362                output_root,
363                output_root_to_root_path,
364                client_root,
365                client_roots: Default::default(),
366                chunk_root_path,
367                source_map_source_type: SourceMapSourceType::TurbopackUri,
368                asset_root_path,
369                asset_root_paths: Default::default(),
370                chunk_base_path: None,
371                worker_asset_prefix: None,
372                asset_suffix: None,
373                asset_base_path: None,
374                asset_base_paths: Default::default(),
375                url_behaviors: Default::default(),
376                default_url_behavior: None,
377                enable_hot_module_replacement: false,
378                enable_tracing: false,
379                enable_nested_async_availability: false,
380                enable_module_merging: false,
381                enable_dynamic_chunk_content_loading: false,
382                debug_ids: false,
383                environment,
384                runtime_type,
385                minify_type: MinifyType::NoMinify,
386                chunk_content_hashing: None,
387                asset_content_hashing: ContentHashing::Direct { length: 13 },
388                source_maps_type: SourceMapsType::Full,
389                current_chunk_method: CurrentChunkMethod::StringLiteral,
390                manifest_chunks: false,
391                module_id_strategy: None,
392                export_usage: None,
393                unused_references: None,
394                chunking_configs: Default::default(),
395                should_use_absolute_url_references: false,
396                worker_forwarded_globals: vec![],
397                chunk_loading_global: Default::default(),
398                hash_salt: ResolvedVc::cell(RcStr::default()),
399                cross_origin: Default::default(),
400            },
401        }
402    }
403}
404impl BrowserChunkingContext {
405    fn generate_evaluate_chunk(
406        self: Vc<Self>,
407        ident: Vc<AssetIdent>,
408        other_chunks: Vc<OutputAssets>,
409        evaluatable_assets: Vc<EvaluatableAssets>,
410        // TODO(sokra) remove this argument and pass chunk items instead
411        module_graph: Vc<ModuleGraph>,
412    ) -> Vc<Box<dyn OutputAsset>> {
413        Vc::upcast(EcmascriptBrowserEvaluateChunk::new(
414            self,
415            ident,
416            other_chunks,
417            evaluatable_assets,
418            module_graph,
419        ))
420    }
421    fn generate_chunk_list_register_chunk(
422        self: Vc<Self>,
423        ident: Vc<AssetIdent>,
424        evaluatable_assets: Vc<EvaluatableAssets>,
425        other_chunks: Vc<OutputAssets>,
426        source: EcmascriptDevChunkListSource,
427    ) -> Vc<Box<dyn OutputAsset>> {
428        Vc::upcast(EcmascriptDevChunkList::new(
429            self,
430            ident,
431            evaluatable_assets,
432            other_chunks,
433            source,
434        ))
435    }
436    async fn generate_chunk(
437        self: Vc<Self>,
438        chunk: ResolvedVc<Box<dyn Chunk>>,
439    ) -> Result<ResolvedVc<Box<dyn OutputAsset>>> {
440        Ok(
441            if let Some(ecmascript_chunk) = ResolvedVc::try_downcast_type::<EcmascriptChunk>(chunk)
442            {
443                ResolvedVc::upcast(
444                    EcmascriptBrowserChunk::new(self, *ecmascript_chunk)
445                        .to_resolved()
446                        .await?,
447                )
448            } else if let Some(output_asset) =
449                ResolvedVc::try_sidecast::<Box<dyn OutputAsset>>(chunk)
450            {
451                output_asset
452            } else {
453                bail!("Unable to generate output asset for chunk");
454            },
455        )
456    }
457}
458
459#[turbo_tasks::value_impl]
460impl BrowserChunkingContext {
461    #[turbo_tasks::function]
462    pub fn current_chunk_method(&self) -> Vc<CurrentChunkMethod> {
463        self.current_chunk_method.cell()
464    }
465
466    #[turbo_tasks::function]
467    pub fn hash_salt(&self) -> Vc<RcStr> {
468        *self.hash_salt
469    }
470
471    /// Returns the kind of runtime to include in output chunks.
472    ///
473    /// This is defined directly on `BrowserChunkingContext` so it is zero-cost
474    /// when `RuntimeType` has a single variant.
475    #[turbo_tasks::function]
476    pub fn runtime_type(&self) -> Vc<RuntimeType> {
477        self.runtime_type.cell()
478    }
479
480    /// Returns the asset base path.
481    #[turbo_tasks::function]
482    pub fn chunk_base_path(&self) -> Vc<Option<RcStr>> {
483        Vc::cell(self.chunk_base_path.clone())
484    }
485
486    /// Returns the worker base-path override. When `Some`, takes precedence
487    /// over `chunk_base_path` for the entrypoint URL and the module chunks
488    /// loaded inside the worker.
489    #[turbo_tasks::function]
490    pub fn worker_asset_prefix(&self) -> Vc<Option<RcStr>> {
491        Vc::cell(self.worker_asset_prefix.clone())
492    }
493
494    /// Returns the asset suffix path.
495    #[turbo_tasks::function]
496    pub fn asset_suffix(&self) -> Vc<AssetSuffix> {
497        if let Some(asset_suffix) = self.asset_suffix {
498            *asset_suffix
499        } else {
500            AssetSuffix::None.cell()
501        }
502    }
503
504    /// Returns the source map type.
505    #[turbo_tasks::function]
506    pub fn source_maps_type(&self) -> Vc<SourceMapsType> {
507        self.source_maps_type.cell()
508    }
509
510    /// Returns the minify type.
511    #[turbo_tasks::function]
512    pub fn minify_type(&self) -> Vc<MinifyType> {
513        self.minify_type.cell()
514    }
515
516    /// Returns the chunk path information.
517    #[turbo_tasks::function]
518    fn chunk_path_info(&self) -> Vc<ChunkPathInfo> {
519        ChunkPathInfo {
520            root_path: self.root_path.clone(),
521            chunk_root_path: self.chunk_root_path.clone(),
522            chunk_content_hashing: self.chunk_content_hashing,
523        }
524        .cell()
525    }
526
527    /// Returns the chunk loading global variable name.
528    /// Defaults to "TURBOPACK" if not set.
529    #[turbo_tasks::function]
530    pub fn chunk_loading_global(&self) -> Vc<RcStr> {
531        Vc::cell(
532            self.chunk_loading_global
533                .clone()
534                .unwrap_or_else(|| rcstr!("TURBOPACK")),
535        )
536    }
537
538    #[turbo_tasks::function]
539    pub fn cross_origin(&self) -> Vc<CrossOrigin> {
540        self.cross_origin.cell()
541    }
542}
543
544#[turbo_tasks::value_impl]
545impl ChunkingContext for BrowserChunkingContext {
546    #[turbo_tasks::function]
547    fn name(&self) -> Vc<RcStr> {
548        if let Some(name) = &self.name {
549            Vc::cell(name.clone())
550        } else {
551            Vc::cell(rcstr!("unknown"))
552        }
553    }
554
555    #[turbo_tasks::function]
556    fn root_path(&self) -> Vc<FileSystemPath> {
557        self.root_path.clone().cell()
558    }
559
560    #[turbo_tasks::function]
561    fn output_root(&self) -> Vc<FileSystemPath> {
562        self.output_root.clone().cell()
563    }
564
565    #[turbo_tasks::function]
566    fn output_root_to_root_path(&self) -> Vc<RcStr> {
567        Vc::cell(self.output_root_to_root_path.clone())
568    }
569
570    #[turbo_tasks::function]
571    fn environment(&self) -> Vc<Environment> {
572        *self.environment
573    }
574
575    #[turbo_tasks::function]
576    fn chunk_root_path(&self) -> Vc<FileSystemPath> {
577        self.chunk_root_path.clone().cell()
578    }
579
580    #[turbo_tasks::function]
581    async fn chunk_path(
582        self: Vc<Self>,
583        asset: Option<Vc<Box<dyn Asset>>>,
584        ident: Vc<AssetIdent>,
585        prefix: Option<RcStr>,
586        extension: RcStr,
587    ) -> Result<Vc<FileSystemPath>> {
588        debug_assert!(
589            extension.starts_with("."),
590            "`extension` should include the leading '.', got '{extension}'"
591        );
592        let ChunkPathInfo {
593            chunk_root_path,
594            chunk_content_hashing,
595            root_path,
596        } = &*self.chunk_path_info().await?;
597        let name = match *chunk_content_hashing {
598            None => {
599                ident
600                    .output_name(root_path.clone(), prefix, extension)
601                    .owned()
602                    .await?
603            }
604            Some(ContentHashing::Direct { length }) => {
605                let Some(asset) = asset else {
606                    bail!("chunk_path requires an asset when content hashing is enabled");
607                };
608                let hash = asset
609                    .content()
610                    .content_hash(self.hash_salt(), HashAlgorithm::Xxh3Hash128Base38)
611                    .await?;
612                let hash = hash.as_ref().context(
613                    "chunk_path requires an asset with file content when content hashing is \
614                     enabled",
615                )?;
616                let hash = &hash[..length as usize];
617                if let Some(prefix) = prefix {
618                    format!("{prefix}-{hash}{extension}").into()
619                } else {
620                    format!("{hash}{extension}").into()
621                }
622            }
623        };
624        Ok(chunk_root_path.join(&name)?.cell())
625    }
626
627    #[turbo_tasks::function]
628    async fn asset_url(&self, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>> {
629        let asset_path = ident.to_string();
630
631        let client_root = tag
632            .as_ref()
633            .and_then(|tag| self.client_roots.get(tag))
634            .unwrap_or(&self.client_root);
635
636        let asset_base_path = tag
637            .as_ref()
638            .and_then(|tag| self.asset_base_paths.get(tag))
639            .or(self.asset_base_path.as_ref());
640
641        let asset_path = asset_path
642            .strip_prefix(&format!("{}/", client_root.path))
643            .context("expected asset_path to contain client_root")?;
644
645        Ok(Vc::cell(
646            format!(
647                "{}{}",
648                asset_base_path.map(|s| s.as_str()).unwrap_or("/"),
649                asset_path
650            )
651            .into(),
652        ))
653    }
654
655    #[turbo_tasks::function]
656    fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
657        Vc::cell(match self.source_maps_type {
658            SourceMapsType::Full => true,
659            SourceMapsType::Partial => true,
660            SourceMapsType::None => false,
661        })
662    }
663
664    #[turbo_tasks::function]
665    fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
666        Vc::cell(match self.source_maps_type {
667            SourceMapsType::Full => true,
668            SourceMapsType::Partial => true,
669            SourceMapsType::None => false,
670        })
671    }
672
673    #[turbo_tasks::function]
674    async fn asset_path(
675        self: Vc<Self>,
676        content: Vc<AssetContent>,
677        original_asset_ident: Vc<AssetIdent>,
678        tag: Option<RcStr>,
679    ) -> Result<Vc<FileSystemPath>> {
680        let this = self.await?;
681        let source_path = original_asset_ident.path().await?;
682        let basename = source_path.file_name();
683        let ContentHashing::Direct { length } = this.asset_content_hashing;
684        let hash = content
685            .content_hash(self.hash_salt(), HashAlgorithm::Xxh3Hash128Base38)
686            .await?;
687        let hash = hash
688            .as_ref()
689            .context("Missing content when trying to generate the content hash for static asset")?;
690        let short_hash = &hash[..length as usize];
691        let asset_path = match source_path.extension() {
692            Some(ext) => format!(
693                "{basename}.{short_hash}.{ext}",
694                basename = &basename[..basename.len() - ext.len() - 1],
695            ),
696            None => format!("{basename}.{short_hash}"),
697        };
698
699        let asset_root_path = tag
700            .as_ref()
701            .and_then(|tag| this.asset_root_paths.get(tag))
702            .unwrap_or(&this.asset_root_path);
703
704        Ok(asset_root_path.join(&asset_path)?.cell())
705    }
706
707    #[turbo_tasks::function]
708    fn url_behavior(&self, tag: Option<RcStr>) -> Vc<UrlBehavior> {
709        tag.as_ref()
710            .and_then(|tag| self.url_behaviors.get(tag))
711            .cloned()
712            .or_else(|| self.default_url_behavior.clone())
713            .unwrap_or(UrlBehavior {
714                suffix: AssetSuffix::Inferred,
715                static_suffix: ResolvedVc::cell(None),
716            })
717            .cell()
718    }
719
720    #[turbo_tasks::function]
721    fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
722        Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
723    }
724
725    #[turbo_tasks::function]
726    fn source_map_source_type(&self) -> Vc<SourceMapSourceType> {
727        self.source_map_source_type.cell()
728    }
729
730    #[turbo_tasks::function]
731    fn is_tracing_enabled(&self) -> Vc<bool> {
732        Vc::cell(self.enable_tracing)
733    }
734
735    #[turbo_tasks::function]
736    fn is_nested_async_availability_enabled(&self) -> Vc<bool> {
737        Vc::cell(self.enable_nested_async_availability)
738    }
739
740    #[turbo_tasks::function]
741    fn is_module_merging_enabled(&self) -> Vc<bool> {
742        Vc::cell(self.enable_module_merging)
743    }
744
745    #[turbo_tasks::function]
746    fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
747        Vc::cell(self.enable_dynamic_chunk_content_loading)
748    }
749
750    #[turbo_tasks::function]
751    pub fn minify_type(&self) -> Vc<MinifyType> {
752        self.minify_type.cell()
753    }
754
755    #[turbo_tasks::function]
756    fn should_use_absolute_url_references(&self) -> Vc<bool> {
757        Vc::cell(self.should_use_absolute_url_references)
758    }
759
760    #[turbo_tasks::function]
761    async fn chunk_group(
762        self: ResolvedVc<Self>,
763        ident: Vc<AssetIdent>,
764        chunk_group: ChunkGroup,
765        module_graph: Vc<ModuleGraph>,
766        availability_info: AvailabilityInfo,
767    ) -> Result<Vc<ChunkGroupResult>> {
768        let span = tracing::info_span!("chunking", name = display(ident.to_string().await?));
769        async move {
770            let this = self.await?;
771            let entries = chunk_group.entries();
772            let input_availability_info = availability_info;
773            let MakeChunkGroupResult {
774                chunks,
775                referenced_output_assets,
776                references,
777                availability_info,
778            } = make_chunk_group(
779                entries,
780                module_graph,
781                ResolvedVc::upcast(self),
782                input_availability_info,
783            )
784            .await?;
785
786            let chunks = chunks.await?;
787
788            let mut assets = chunks
789                .iter()
790                .map(|chunk| self.generate_chunk(*chunk))
791                .try_join()
792                .await?;
793
794            if this.enable_hot_module_replacement {
795                let mut ident = ident;
796                if let Some(input_availability_info_ident) = input_availability_info.ident().await?
797                {
798                    ident = ident.with_modifier(input_availability_info_ident);
799                }
800                let other_assets = Vc::cell(assets.clone());
801                assets.push(
802                    self.generate_chunk_list_register_chunk(
803                        ident,
804                        EvaluatableAssets::empty(),
805                        other_assets,
806                        EcmascriptDevChunkListSource::Dynamic,
807                    )
808                    .to_resolved()
809                    .await?,
810                );
811            }
812
813            Ok(ChunkGroupResult {
814                assets: ResolvedVc::cell(assets),
815                referenced_assets: ResolvedVc::cell(referenced_output_assets),
816                references: ResolvedVc::cell(references),
817                availability_info,
818            }
819            .cell())
820        }
821        .instrument(span)
822        .await
823    }
824
825    #[turbo_tasks::function]
826    async fn evaluated_chunk_group(
827        self: ResolvedVc<Self>,
828        ident: Vc<AssetIdent>,
829        chunk_group: ChunkGroup,
830        module_graph: Vc<ModuleGraph>,
831        input_availability_info: AvailabilityInfo,
832    ) -> Result<Vc<ChunkGroupResult>> {
833        let span = tracing::info_span!(
834            "chunking",
835            name = display(ident.to_string().await?),
836            chunking_type = "evaluated",
837        );
838        async move {
839            let this = self.await?;
840            let entries = chunk_group.entries();
841            let MakeChunkGroupResult {
842                chunks,
843                referenced_output_assets,
844                references,
845                availability_info,
846            } = make_chunk_group(
847                entries,
848                module_graph,
849                ResolvedVc::upcast(self),
850                input_availability_info,
851            )
852            .await?;
853
854            let chunks = chunks.await?;
855
856            let mut assets: Vec<ResolvedVc<Box<dyn OutputAsset>>> = chunks
857                .iter()
858                .map(|chunk| self.generate_chunk(*chunk))
859                .try_join()
860                .await?;
861
862            let other_assets = Vc::cell(assets.clone());
863
864            let entries = Vc::cell(
865                chunk_group
866                    .entries()
867                    .map(|m| {
868                        ResolvedVc::try_downcast::<Box<dyn EvaluatableAsset>>(m)
869                            .context("evaluated_chunk_group entries must be evaluatable assets")
870                    })
871                    .collect::<Result<Vec<_>>>()?,
872            );
873
874            if this.enable_hot_module_replacement {
875                let mut ident = ident;
876                if let Some(input_availability_info_ident) = input_availability_info.ident().await?
877                {
878                    ident = ident.with_modifier(input_availability_info_ident);
879                }
880                assets.push(
881                    self.generate_chunk_list_register_chunk(
882                        ident,
883                        entries,
884                        other_assets,
885                        EcmascriptDevChunkListSource::Entry,
886                    )
887                    .to_resolved()
888                    .await?,
889                );
890            }
891
892            assets.push(
893                self.generate_evaluate_chunk(ident, other_assets, entries, module_graph)
894                    .to_resolved()
895                    .await?,
896            );
897
898            Ok(ChunkGroupResult {
899                assets: ResolvedVc::cell(assets),
900                referenced_assets: ResolvedVc::cell(referenced_output_assets),
901                references: ResolvedVc::cell(references),
902                availability_info,
903            }
904            .cell())
905        }
906        .instrument(span)
907        .await
908    }
909
910    #[turbo_tasks::function]
911    fn entry_chunk_group(
912        self: Vc<Self>,
913        _path: FileSystemPath,
914        _chunk_group: ChunkGroup,
915        _module_graph: Vc<ModuleGraph>,
916        _extra_chunks: Vc<OutputAssets>,
917        _extra_referenced_assets: Vc<OutputAssets>,
918        _availability_info: AvailabilityInfo,
919    ) -> Result<Vc<EntryChunkGroupResult>> {
920        bail!("Browser chunking context does not support entry chunk groups")
921    }
922
923    #[turbo_tasks::function]
924    fn chunk_item_id_strategy(&self) -> Vc<ModuleIdStrategy> {
925        *self
926            .module_id_strategy
927            .unwrap_or_else(|| ModuleIdStrategy::default().resolved_cell())
928    }
929
930    #[turbo_tasks::function]
931    async fn async_loader_chunk_item(
932        self: ResolvedVc<Self>,
933        module: Vc<Box<dyn ChunkableModule>>,
934        module_graph: Vc<ModuleGraph>,
935        availability_info: AvailabilityInfo,
936    ) -> Result<Vc<Box<dyn ChunkItem>>> {
937        let chunking_context = ResolvedVc::upcast::<Box<dyn ChunkingContext>>(self);
938        Ok(if self.await?.manifest_chunks {
939            let manifest_asset = ManifestAsyncModule::new(
940                module,
941                module_graph,
942                *chunking_context,
943                availability_info,
944            );
945            let loader_module = ManifestLoaderModule::new(manifest_asset);
946            loader_module.as_chunk_item(module_graph, *chunking_context)
947        } else {
948            let module = AsyncLoaderModule::new(module, *chunking_context, availability_info);
949            module.as_chunk_item(module_graph, *chunking_context)
950        })
951    }
952
953    #[turbo_tasks::function]
954    async fn async_loader_chunk_item_ident(
955        self: Vc<Self>,
956        module: Vc<Box<dyn ChunkableModule>>,
957    ) -> Result<Vc<AssetIdent>> {
958        Ok(if self.await?.manifest_chunks {
959            ManifestLoaderModule::asset_ident_for(module)
960        } else {
961            AsyncLoaderModule::asset_ident_for(module)
962        })
963    }
964
965    #[turbo_tasks::function]
966    async fn module_export_usage(
967        &self,
968        module: ResolvedVc<Box<dyn Module>>,
969    ) -> Result<Vc<ModuleExportUsage>> {
970        if let Some(export_usage) = self.export_usage {
971            Ok(export_usage.await?.used_exports(module).await?)
972        } else {
973            Ok(ModuleExportUsage::all())
974        }
975    }
976
977    #[turbo_tasks::function]
978    fn unused_references(&self) -> Vc<UnusedReferences> {
979        if let Some(unused_references) = self.unused_references {
980            *unused_references
981        } else {
982            Vc::cell(Default::default())
983        }
984    }
985
986    #[turbo_tasks::function]
987    async fn debug_ids_enabled(self: Vc<Self>) -> Result<Vc<bool>> {
988        Ok(Vc::cell(self.await?.debug_ids))
989    }
990
991    #[turbo_tasks::function]
992    fn worker_forwarded_globals(&self) -> Vc<Vec<RcStr>> {
993        Vc::cell(self.worker_forwarded_globals.clone())
994    }
995
996    #[turbo_tasks::function]
997    async fn worker_entrypoint(self: Vc<Self>) -> Result<Vc<Box<dyn OutputAsset>>> {
998        let chunking_context: Vc<Box<dyn ChunkingContext>> = Vc::upcast(self);
999        let resolved = chunking_context.to_resolved().await?;
1000        let forwarded_globals = chunking_context.worker_forwarded_globals();
1001        let entrypoint = EcmascriptBrowserWorkerEntrypoint::new(*resolved, forwarded_globals);
1002        Ok(Vc::upcast(entrypoint))
1003    }
1004}
1005
1006#[turbo_tasks::value]
1007struct ChunkPathInfo {
1008    root_path: FileSystemPath,
1009    chunk_root_path: FileSystemPath,
1010    chunk_content_hashing: Option<ContentHashing>,
1011}