1use std::{borrow::Cow, sync::Arc};
2
3use anyhow::{Context as _, Result, bail};
4use bincode::{Decode, Encode};
5use swc_core::{
6 common::{
7 DUMMY_SP, Span,
8 errors::{DiagnosticId, Handler},
9 },
10 ecma::{
11 ast::{
12 Expr, ExprStmt, KeyValueProp, Lit, ModuleItem, ObjectLit, Prop, PropName, PropOrSpread,
13 Stmt, {self},
14 },
15 codegen::{Emitter, text_writer::JsWriter},
16 },
17 quote, quote_expr,
18};
19use turbo_rcstr::{RcStr, rcstr};
20use turbo_tasks::{
21 FxIndexMap, NonLocalValue, ResolvedVc, TryJoinIterExt, ValueToString, Vc,
22 debug::ValueDebugFormat,
23};
24use turbo_tasks_fs::{
25 DirectoryEntry, FileSystemPath, ReadGlobResult,
26 glob::{Glob, GlobOptions, relativize_glob},
27};
28use turbopack_core::{
29 chunk::{
30 AsyncModuleInfo, ChunkableModule, ChunkingContext, ChunkingType, MinifyType,
31 ModuleChunkItemIdExt,
32 },
33 ident::AssetIdent,
34 issue::{IssueExt, IssueSeverity, IssueSource, StyledString, code_gen::CodeGenerationIssue},
35 module::{Module, ModuleSideEffects},
36 module_graph::ModuleGraph,
37 reference::{ModuleReference, ModuleReferences},
38 reference_type::EcmaScriptModulesReferenceSubType,
39 resolve::{
40 BindingUsage, ExportUsage, ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin,
41 parse::Request,
42 },
43};
44use turbopack_resolve::ecmascript::esm_resolve;
45
46use crate::{
47 EcmascriptChunkPlaceable,
48 analyzer::JsValue,
49 ast_path_trie::{AstPathId, AstPathTrie, AstPathTrieBuilder},
50 chunk::{EcmascriptChunkItemContent, EcmascriptExports, ecmascript_chunk_item},
51 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
52 create_visitor,
53 references::pattern_mapping::{PatternMapping, ResolveType},
54 runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_REQUIRE},
55 utils::module_id_to_lit,
56};
57
58#[derive(Debug, Clone)]
64pub struct ImportMetaGlobOptions {
65 pub patterns: Vec<RcStr>,
67 pub eager: bool,
69 pub import: Option<RcStr>,
71 pub query: Option<RcStr>,
73 pub base: Option<RcStr>,
75 pub case_sensitive: bool,
77}
78
79pub fn parse_import_meta_glob(
91 args: &[JsValue<'_>],
92 handler: &Handler,
93 span: Span,
94 diagnostic_id: DiagnosticId,
95) -> Option<ImportMetaGlobOptions> {
96 if args.is_empty() || args.len() > 2 {
97 handler.span_warn_with_code(
98 span,
99 "import.meta.glob() requires 1 or 2 arguments",
100 diagnostic_id,
101 );
102 return None;
103 }
104
105 let patterns = {
107 let mut pats = Vec::new();
108 match &args[0] {
109 JsValue::Array { items, .. } => {
110 for item in items {
111 if let Some(s) = item.as_str() {
112 pats.push(s.into());
113 } else {
114 handler.span_warn_with_code(
115 span,
116 "import.meta.glob() pattern array elements must be constant strings",
117 diagnostic_id,
118 );
119 return None;
120 }
121 }
122 if pats.is_empty() {
123 handler.span_warn_with_code(
124 span,
125 "import.meta.glob() requires at least one pattern",
126 diagnostic_id,
127 );
128 return None;
129 }
130 }
131 _ => {
132 if let Some(s) = args[0].as_str() {
133 pats.push(s.into());
134 } else {
135 handler.span_warn_with_code(
136 span,
137 "import.meta.glob() first argument must be a string literal or array of \
138 string literals",
139 diagnostic_id,
140 );
141 return None;
142 }
143 }
144 }
145 pats
146 };
147
148 let mut eager = false;
150 let mut import = None;
151 let mut query = None;
152 let mut base = None;
153 let mut case_sensitive = true;
154
155 if let Some(opts) = args.get(1) {
156 match opts {
157 JsValue::Object { parts, .. } => {
158 use crate::analyzer::ObjectPart;
159 for part in parts {
160 if let ObjectPart::KeyValue(key, val) = part {
161 match key.as_str() {
162 Some("eager") => {
163 if let Some(b) = val.as_bool() {
164 eager = b;
165 } else {
166 handler.span_warn_with_code(
167 span,
168 "import.meta.glob() 'eager' option must be a constant \
169 boolean (true or false), defaulting to false",
170 diagnostic_id.clone(),
171 );
172 }
173 }
174 Some("import") => {
175 if let Some(s) = val.as_str() {
176 if s != "*" {
179 import = Some(s.into());
180 }
181 } else {
182 handler.span_warn_with_code(
183 span,
184 "import.meta.glob() 'import' option must be a constant \
185 string, ignoring",
186 diagnostic_id.clone(),
187 );
188 }
189 }
190 Some("query") => {
191 if let Some(s) = val.as_str() {
192 let q: RcStr = if s.starts_with('?') {
194 s.into()
195 } else {
196 format!("?{s}").into()
197 };
198 query = Some(q);
199 } else if let JsValue::Object { parts, .. } = val {
200 use crate::analyzer::ObjectPart;
203 let mut pairs: Vec<String> = Vec::new();
204 for part in parts {
205 if let ObjectPart::KeyValue(k, v) = part {
206 if let Some(k_str) = k.as_str() {
207 let enc_key = urlencoding::encode(k_str);
208 if let Some(v_str) = v.as_str() {
209 let enc_val = urlencoding::encode(v_str);
210 pairs.push(format!("{enc_key}={enc_val}"));
211 } else if let Some(v_bool) = v.as_bool() {
212 pairs.push(format!("{enc_key}={v_bool}"));
213 } else {
214 handler.span_warn_with_code(
215 span,
216 &format!(
217 "import.meta.glob() 'query' object \
218 value for key '{k_str}' must be a \
219 constant string or boolean, ignoring"
220 ),
221 diagnostic_id.clone(),
222 );
223 }
224 } else {
225 handler.span_warn_with_code(
226 span,
227 "import.meta.glob() 'query' object keys must \
228 be constant strings",
229 diagnostic_id.clone(),
230 );
231 }
232 } else {
233 handler.span_warn_with_code(
234 span,
235 "import.meta.glob() 'query' object must only \
236 contain constant key-value pairs",
237 diagnostic_id.clone(),
238 );
239 }
240 }
241 if !pairs.is_empty() {
242 query = Some(format!("?{}", pairs.join("&")).into());
243 }
244 } else {
245 handler.span_warn_with_code(
246 span,
247 "import.meta.glob() 'query' option must be a constant \
248 string, ignoring",
249 diagnostic_id.clone(),
250 );
251 }
252 }
253 Some("base") => {
254 if let Some(s) = val.as_str() {
255 base = Some(s.into());
256 } else {
257 handler.span_warn_with_code(
258 span,
259 "import.meta.glob() 'base' option must be a constant \
260 string, ignoring",
261 diagnostic_id.clone(),
262 );
263 }
264 }
265 Some("caseSensitive") => {
266 if let Some(b) = val.as_bool() {
267 case_sensitive = b;
268 } else {
269 handler.span_warn_with_code(
270 span,
271 "import.meta.glob() 'caseSensitive' option must be a \
272 constant boolean (true or false), defaulting to true",
273 diagnostic_id.clone(),
274 );
275 }
276 }
277 Some("as") => {
280 handler.span_warn_with_code(
281 span,
282 "import.meta.glob() 'as' option is not supported. Use 'query' \
283 instead (e.g. { query: '?raw' })",
284 diagnostic_id.clone(),
285 );
286 }
287 Some(other) => {
288 handler.span_warn_with_code(
289 span,
290 &format!(
291 "import.meta.glob() unsupported option '{other}'. \
292 Supported options are: eager, import, query, base, \
293 caseSensitive"
294 ),
295 diagnostic_id.clone(),
296 );
297 }
298 None => {
299 handler.span_warn_with_code(
300 span,
301 "import.meta.glob() option keys must be constant strings",
302 diagnostic_id.clone(),
303 );
304 }
305 }
306 }
307 }
308 }
309 _ => {
310 handler.span_err_with_code(
311 span,
312 "import.meta.glob() second argument must be an object literal",
313 diagnostic_id.clone(),
314 );
315 return None;
316 }
317 }
318 }
319
320 Some(ImportMetaGlobOptions {
321 patterns,
322 eager,
323 import,
324 query,
325 base,
326 case_sensitive,
327 })
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336enum PatternRoot {
337 ProjectRoot,
339 Relative,
341}
342
343fn split_pattern<'a>(
361 pattern: &'a str,
362 base_dir: &FileSystemPath,
363 root: Option<&FileSystemPath>,
364) -> Option<(PatternRoot, &'a str, FileSystemPath)> {
365 if let Some(rest) = pattern.strip_prefix('/') {
366 return Some((PatternRoot::ProjectRoot, rest, root?.clone()));
367 }
368 let (rest, dir) = relativize_glob(pattern, base_dir)?;
369 Some((PatternRoot::Relative, rest, dir))
370}
371
372fn strip_negation(pattern: &str) -> &str {
374 pattern.strip_prefix('!').unwrap_or(pattern)
375}
376
377struct SplitPattern<'a> {
381 negative: bool,
383 root: PatternRoot,
385 rest: &'a str,
387 root_dir: FileSystemPath,
389}
390
391async fn flatten_read_glob(result: &ReadGlobResult) -> Result<Vec<(RcStr, FileSystemPath)>> {
403 let mut files = Vec::new();
404
405 fn collect_files(
407 node: &ReadGlobResult,
408 prefix: &str,
409 files: &mut Vec<(RcStr, FileSystemPath)>,
410 ) {
411 for (segment, entry) in &node.results {
412 let full_path = if prefix.is_empty() {
413 segment.to_string()
414 } else {
415 format!("{prefix}/{segment}")
416 };
417 if let DirectoryEntry::File(path) = entry {
418 files.push((full_path.into(), path.clone()));
419 }
420 }
421 }
422
423 let mut pending: Vec<(String, turbo_tasks::ReadRef<ReadGlobResult>)> = Vec::new();
425 collect_files(result, "", &mut files);
426
427 for (segment, inner_vc) in &result.inner {
429 let child_prefix = segment.to_string();
430 let inner = inner_vc.await?;
431 pending.push((child_prefix, inner));
432 }
433
434 while let Some((prefix, node)) = pending.pop() {
435 collect_files(&node, &prefix, &mut files);
436 for (segment, inner_vc) in &node.inner {
437 let child_prefix = format!("{prefix}/{segment}");
438 let inner = inner_vc.await?;
439 pending.push((child_prefix, inner));
440 }
441 }
442
443 files.sort_by(|a: &(RcStr, _), b: &(RcStr, _)| a.0.cmp(&b.0));
444 Ok(files)
445}
446
447#[turbo_tasks::value]
452#[derive(Debug)]
453pub struct ImportMetaGlobMapEntry {
454 pub origin_relative: RcStr,
457 pub request: ResolvedVc<Request>,
458 pub result: ResolvedVc<ModuleResolveResult>,
459}
460
461#[turbo_tasks::value(transparent)]
462pub struct ImportMetaGlobMap(
463 #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, ImportMetaGlobMapEntry>,
464);
465
466#[turbo_tasks::value_impl]
467impl ImportMetaGlobMap {
468 #[turbo_tasks::function]
484 pub(crate) async fn generate(
485 origin: Vc<Box<dyn ResolveOrigin>>,
486 scan_dir: FileSystemPath,
487 positive_glob: Vc<Glob>,
488 negative_glob: Option<Vc<Glob>>,
489 root_absolute_glob: Option<Vc<Glob>>,
490 root: Option<FileSystemPath>,
491 key_base: Option<FileSystemPath>,
492 query: Option<RcStr>,
493 eager: bool,
494 lazy_compilation: bool,
495 issue_source: Option<IssueSource>,
496 error_mode: ResolveErrorMode,
497 ) -> Result<Vc<Self>> {
498 let origin_path = origin.into_trait_ref().await?.origin_path().parent();
499
500 let glob_result = scan_dir.read_glob(positive_glob).await?;
502 let files = flatten_read_glob(&glob_result).await?;
503
504 let negative = if let Some(neg) = negative_glob {
506 Some(neg.await?)
507 } else {
508 None
509 };
510 let root_absolute = if let Some(glob) = root_absolute_glob {
511 Some(glob.await?)
512 } else {
513 None
514 };
515
516 let reference_sub_type = if eager {
517 EcmaScriptModulesReferenceSubType::Import
518 } else if lazy_compilation {
519 EcmaScriptModulesReferenceSubType::LazyDynamicImport
522 } else {
523 EcmaScriptModulesReferenceSubType::DynamicImport
524 };
525
526 let entries: Vec<_> = files
528 .iter()
529 .filter(|(scan_relative, _)| {
530 if let Some(ref neg) = negative {
532 !neg.matches(scan_relative)
533 } else {
534 true
535 }
536 })
537 .map(|(scan_relative, _logical_path)| {
538 let origin_path = &origin_path;
539 let scan_dir = &scan_dir;
540 let query = &query;
541 let reference_sub_type = &reference_sub_type;
542 let key_base = &key_base;
543 let root_absolute = &root_absolute;
544 let root = &root;
545 async move {
546 let logical_path = scan_dir.join(scan_relative)?;
550 let Some(origin_relative) = origin_path.get_relative_request_to(&logical_path)
551 else {
552 bail!(
553 "import.meta.glob: failed to compute relative path from origin to \
554 matched file"
555 );
556 };
557
558 let key: RcStr = if let Some(key_base) = key_base {
560 let Some(key) = key_base.get_relative_request_to(&logical_path) else {
564 bail!(
565 "import.meta.glob: failed to compute relative path from base to \
566 matched file"
567 );
568 };
569 key
570 } else if root_absolute
571 .as_ref()
572 .is_some_and(|glob| glob.matches(scan_relative))
573 {
574 let root = root
577 .as_ref()
578 .context("a project-root-absolute pattern requires a root")?;
579 let Some(relative) = root.get_relative_path_to(&logical_path) else {
580 bail!(
581 "import.meta.glob: failed to compute relative path from the root \
582 of the project to matched file"
583 );
584 };
585 format!("/{relative}").into()
586 } else {
587 origin_relative.clone()
588 };
589
590 let request_str: RcStr = if let Some(q) = query {
592 format!("{origin_relative}{q}").into()
593 } else {
594 origin_relative.clone()
595 };
596
597 let request = Request::parse_string(request_str).to_resolved().await?;
598
599 let result = esm_resolve(
600 origin,
601 *request,
602 reference_sub_type.clone(),
603 error_mode,
604 issue_source,
605 )
606 .await?
607 .to_resolved()
608 .await?;
609
610 Ok((
611 key,
612 ImportMetaGlobMapEntry {
613 origin_relative,
614 request,
615 result,
616 },
617 ))
618 }
619 })
620 .try_join()
621 .await?;
622
623 let mut map: FxIndexMap<RcStr, ImportMetaGlobMapEntry> = entries.into_iter().collect();
624
625 map.sort_keys();
626
627 Ok(Vc::cell(map))
628 }
629}
630
631#[turbo_tasks::value]
640#[derive(ValueToString)]
641#[value_to_string("import.meta.glob resolved reference")]
642pub struct ImportMetaGlobModuleReference {
643 result: ResolvedVc<ModuleResolveResult>,
644 export: ExportUsage,
645 eager: bool,
646}
647
648#[turbo_tasks::value_impl]
649impl ModuleReference for ImportMetaGlobModuleReference {
650 #[turbo_tasks::function]
651 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
652 *self.result
653 }
654
655 fn chunking_type(&self) -> Option<ChunkingType> {
656 Some(if self.eager {
658 ChunkingType::Parallel {
659 inherit_async: false,
660 hoisted: false,
661 }
662 } else {
663 ChunkingType::Async
664 })
665 }
666
667 fn binding_usage(&self) -> BindingUsage {
668 BindingUsage {
669 import: Default::default(),
670 export: self.export.clone(),
671 }
672 }
673}
674
675fn modifier(
685 patterns: &[RcStr],
686 eager: bool,
687 lazy_compilation: bool,
688 import: &Option<RcStr>,
689 query: &Option<RcStr>,
690 base: &Option<RcStr>,
691 case_sensitive: bool,
692) -> RcStr {
693 let mut s = format!("import.meta.glob {}", patterns.join(", "));
694 if eager {
695 s.push_str(" eager");
696 }
697 if lazy_compilation {
698 s.push_str(" lazy-compilation");
699 }
700 if let Some(named) = import {
701 s.push_str(" import=");
702 s.push_str(named);
703 }
704 if let Some(q) = query {
705 s.push_str(" query=");
706 s.push_str(q);
707 }
708 if let Some(b) = base {
709 s.push_str(" base=");
710 s.push_str(b);
711 }
712 if !case_sensitive {
713 s.push_str(" case-insensitive");
714 }
715 s.into()
716}
717
718#[turbo_tasks::value]
719pub struct ImportMetaGlobAsset {
720 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
721 pub patterns: Vec<RcStr>,
722 pub eager: bool,
723 pub lazy_compilation: bool,
726 pub import: Option<RcStr>,
727 pub query: Option<RcStr>,
728 pub base: Option<RcStr>,
729 pub case_sensitive: bool,
730 pub issue_source: Option<IssueSource>,
731 pub error_mode: ResolveErrorMode,
732}
733
734#[turbo_tasks::value_impl]
735impl ImportMetaGlobAsset {
736 #[turbo_tasks::function]
743 pub async fn map(&self) -> Result<Vc<ImportMetaGlobMap>> {
744 let origin = *self.origin;
745 let origin_dir = origin.into_trait_ref().await?.origin_path().parent();
746
747 let base_dir = if let Some(ref b) = self.base {
750 match origin_dir.try_join(b) {
751 Some(base_dir) => base_dir,
752 None => {
753 emit_escapes_root_issue(self, &origin_dir, &format!("the 'base' option {b:?}"))
754 .await?;
755 return Ok(Vc::cell(Default::default()));
756 }
757 }
758 } else {
759 origin_dir.clone()
760 };
761 let root = self
765 .origin
766 .into_trait_ref()
767 .await?
768 .resolve_options()
769 .await?
770 .server_relative_root
771 .clone();
772
773 let mut patterns = Vec::with_capacity(self.patterns.len());
785 for written in &self.patterns {
786 let negative = written.starts_with('!');
787 let stripped = strip_negation(written);
788 let Some((pattern_root, rest, root_dir)) =
789 split_pattern(stripped, &base_dir, root.as_ref())
790 else {
791 if root.is_none() && stripped.starts_with('/') {
792 emit_absolute_unsupported_issue(self, &format!("the pattern {written:?}"))
793 .await?;
794 } else {
795 emit_escapes_root_issue(self, &origin_dir, &format!("the pattern {written:?}"))
796 .await?;
797 }
798 if negative {
799 return Ok(Vc::cell(Default::default()));
800 }
801 continue;
802 };
803 patterns.push(SplitPattern {
804 negative,
805 root: pattern_root,
806 rest,
807 root_dir,
808 });
809 }
810
811 let mut scan_dir = base_dir.clone();
818 for pattern in &patterns {
819 if pattern.root_dir.path.len() < scan_dir.path.len() {
822 scan_dir = pattern.root_dir.clone();
823 }
824 }
825
826 let glob_options = GlobOptions {
827 case_insensitive: !self.case_sensitive,
828 ..Default::default()
829 };
830
831 let relative_to_scan_dir = |pattern: &SplitPattern<'_>| -> Result<RcStr> {
833 let prefix = if pattern.root_dir == scan_dir {
834 ""
835 } else {
836 scan_dir
837 .get_path_to(&pattern.root_dir)
838 .context("the scanned directory must contain every pattern root")?
839 };
840 Ok(if prefix.is_empty() {
841 pattern.rest.into()
842 } else {
843 format!("{prefix}/{}", pattern.rest).into()
844 })
845 };
846
847 let mut positive_globs: Vec<Vc<Glob>> = Vec::new();
848 let mut negative_globs: Vec<Vc<Glob>> = Vec::new();
849 let mut root_absolute_globs: Vec<Vc<Glob>> = Vec::new();
854 for pattern in &patterns {
855 let glob = Glob::new(relative_to_scan_dir(pattern)?, glob_options);
856 if pattern.negative {
857 negative_globs.push(glob);
858 } else {
859 if pattern.root == PatternRoot::ProjectRoot {
860 root_absolute_globs.push(glob);
861 }
862 positive_globs.push(glob);
863 }
864 }
865
866 let positive_glob = if positive_globs.len() == 1 {
867 positive_globs.into_iter().next().unwrap()
868 } else {
869 Glob::alternatives(positive_globs)
870 };
871
872 let negative_glob = if negative_globs.is_empty() {
874 None
875 } else if negative_globs.len() == 1 {
876 Some(negative_globs.into_iter().next().unwrap())
877 } else {
878 Some(Glob::alternatives(negative_globs))
879 };
880
881 let root_absolute_glob = if root_absolute_globs.is_empty() {
882 None
883 } else if root_absolute_globs.len() == 1 {
884 Some(root_absolute_globs.into_iter().next().unwrap())
885 } else {
886 Some(Glob::alternatives(root_absolute_globs))
887 };
888
889 Ok(ImportMetaGlobMap::generate(
890 origin,
891 scan_dir,
892 positive_glob,
893 negative_glob,
894 root_absolute_glob,
895 root,
896 self.base.is_some().then_some(base_dir),
898 self.query.clone(),
899 self.eager,
900 self.lazy_compilation,
901 self.issue_source,
902 self.error_mode,
903 ))
904 }
905}
906
907async fn emit_absolute_unsupported_issue(asset: &ImportMetaGlobAsset, what: &str) -> Result<()> {
909 CodeGenerationIssue {
910 severity: IssueSeverity::Error,
911 title: StyledString::Text(rcstr!(
912 "import.meta.glob() does not support patterns absolute from the root of the project \
913 here"
914 ))
915 .resolved_cell(),
916 message: StyledString::Text(
917 format!(
918 "{what} of import.meta.glob({}) starts with `/`, but there is no directory to \
919 resolve it from. Use a pattern relative to the importing file instead.",
920 asset
921 .patterns
922 .iter()
923 .map(|p| format!("{p:?}"))
924 .collect::<Vec<_>>()
925 .join(", "),
926 )
927 .into(),
928 )
929 .resolved_cell(),
930 path: asset.origin.into_trait_ref().await?.origin_path(),
931 source: asset.issue_source,
932 }
933 .resolved_cell()
934 .emit();
935 Ok(())
936}
937
938async fn emit_escapes_root_issue(
941 asset: &ImportMetaGlobAsset,
942 origin_dir: &FileSystemPath,
943 what: &str,
944) -> Result<()> {
945 CodeGenerationIssue {
946 severity: IssueSeverity::Error,
947 title: StyledString::Text(rcstr!(
948 "import.meta.glob() cannot look outside of the project root"
949 ))
950 .resolved_cell(),
951 message: StyledString::Text(
952 format!(
953 "{what} of import.meta.glob({}) resolves to a directory above the project root, \
954 relative to {}. Patterns are relative to the importing file, or absolute from \
955 the project root when they start with `/`.",
956 asset
957 .patterns
958 .iter()
959 .map(|p| format!("{p:?}"))
960 .collect::<Vec<_>>()
961 .join(", "),
962 origin_dir.path
963 )
964 .into(),
965 )
966 .resolved_cell(),
967 path: asset.origin.into_trait_ref().await?.origin_path(),
968 source: asset.issue_source,
969 }
970 .resolved_cell()
971 .emit();
972 Ok(())
973}
974
975#[turbo_tasks::value_impl]
976impl Module for ImportMetaGlobAsset {
977 #[turbo_tasks::function]
978 async fn ident(&self) -> Result<Vc<AssetIdent>> {
979 let origin = self.origin.into_trait_ref().await?;
980 let origin_path = origin.origin_path();
981 Ok(AssetIdent::from_path(origin_path)
985 .with_layer(origin.asset_context().into_trait_ref().await?.layer())
986 .with_modifier(modifier(
987 &self.patterns,
988 self.eager,
989 self.lazy_compilation,
990 &self.import,
991 &self.query,
992 &self.base,
993 self.case_sensitive,
994 ))
995 .into_vc())
996 }
997
998 #[turbo_tasks::function]
999 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
1000 Vc::cell(None)
1001 }
1002
1003 #[turbo_tasks::function]
1004 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
1005 let this = self.await?;
1006 let map = &*self.map().await?;
1007
1008 let export = match &this.import {
1009 Some(name) => ExportUsage::Named(name.clone()),
1010 None => ExportUsage::All,
1011 };
1012
1013 for (key, entry) in map.iter() {
1018 if entry.result.await?.primary.iter().any(|(_, item)| {
1019 matches!(
1020 item,
1021 turbopack_core::resolve::ModuleResolveResultItem::Unknown(_)
1022 )
1023 }) {
1024 CodeGenerationIssue {
1025 severity: IssueSeverity::Error,
1026 title: StyledString::Text(rcstr!(
1027 "import.meta.glob() matched a file that has no module type"
1028 ))
1029 .resolved_cell(),
1030 message: StyledString::Text(
1031 format!(
1032 "import.meta.glob({}) matched {key}, which doesn't have an associated \
1033 module type. Narrow the pattern, exclude the file with a negative \
1034 pattern (\"!...\"), or register a loader or module type for its file \
1035 extension.",
1036 this.patterns
1037 .iter()
1038 .map(|p| format!("{p:?}"))
1039 .collect::<Vec<_>>()
1040 .join(", ")
1041 )
1042 .into(),
1043 )
1044 .resolved_cell(),
1045 path: this.origin.into_trait_ref().await?.origin_path(),
1046 source: this.issue_source,
1047 }
1048 .resolved_cell()
1049 .emit();
1050 }
1051 }
1052
1053 Ok(Vc::cell(
1054 map.iter()
1055 .map(|(_, entry)| {
1056 ResolvedVc::upcast(
1057 ImportMetaGlobModuleReference {
1058 result: entry.result,
1059 export: export.clone(),
1060 eager: this.eager,
1061 }
1062 .resolved_cell(),
1063 )
1064 })
1065 .collect(),
1066 ))
1067 }
1068
1069 #[turbo_tasks::function]
1070 fn side_effects(&self) -> Vc<ModuleSideEffects> {
1071 if self.eager {
1072 ModuleSideEffects::ModuleEvaluationIsSideEffectFree.cell()
1076 } else {
1077 ModuleSideEffects::SideEffectFree.cell()
1080 }
1081 }
1082}
1083
1084#[turbo_tasks::value_impl]
1085impl ChunkableModule for ImportMetaGlobAsset {
1086 #[turbo_tasks::function]
1087 fn as_chunk_item(
1088 self: ResolvedVc<Self>,
1089 module_graph: ResolvedVc<ModuleGraph>,
1090 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
1091 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
1092 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
1093 }
1094}
1095
1096#[turbo_tasks::value_impl]
1097impl EcmascriptChunkPlaceable for ImportMetaGlobAsset {
1098 #[turbo_tasks::function]
1099 fn get_exports(&self) -> Vc<EcmascriptExports> {
1100 EcmascriptExports::Value.cell()
1101 }
1102
1103 #[turbo_tasks::function]
1104 async fn chunk_item_content(
1105 self: Vc<Self>,
1106 chunking_context: Vc<Box<dyn ChunkingContext>>,
1107 _module_graph: Vc<ModuleGraph>,
1108 _async_module_info: Option<Vc<AsyncModuleInfo>>,
1109 _estimated: bool,
1110 ) -> Result<Vc<EcmascriptChunkItemContent>> {
1111 let this = self.await?;
1112 let map = &*self.map().await?;
1113 let minify = chunking_context.minify_type().await?;
1114
1115 let resolve_type =
1119 if !this.eager && chunking_context.chunk_loading().await?.can_split_async() {
1120 ResolveType::AsyncChunkLoader
1121 } else {
1122 ResolveType::ChunkItem
1123 };
1124
1125 let mut glob_map = ObjectLit {
1126 span: DUMMY_SP,
1127 props: vec![],
1128 };
1129
1130 for (key, entry) in map {
1131 let pm = PatternMapping::resolve_request(
1132 *entry.request,
1133 *this.origin,
1134 chunking_context,
1135 *entry.result,
1136 resolve_type,
1137 None,
1138 )
1139 .await?;
1140
1141 let PatternMapping::Single(pm) = &*pm else {
1142 continue;
1143 };
1144
1145 let key_expr = Expr::Lit(Lit::Str(entry.origin_relative.as_str().into()));
1146
1147 let value_expr = if this.eager {
1149 let module_expr = pm.create_esm_require(Cow::Borrowed(&key_expr));
1152 if let Some(named) = &this.import {
1154 quote!(
1155 "$module[$named]" as Expr,
1156 module: Expr = module_expr,
1157 named: Expr = Expr::Lit(Lit::Str(named.as_str().into()))
1158 )
1159 } else {
1160 module_expr
1161 }
1162 } else {
1163 let import_expr = pm.create_import(Cow::Borrowed(&key_expr), false);
1165 if let Some(named) = &this.import {
1166 quote!(
1168 "() => $promise.then((m) => m[$named])" as Expr,
1169 promise: Expr = import_expr,
1170 named: Expr = Expr::Lit(Lit::Str(named.as_str().into()))
1171 )
1172 } else {
1173 quote!(
1174 "() => $promise" as Expr,
1175 promise: Expr = import_expr
1176 )
1177 }
1178 };
1179
1180 let prop = KeyValueProp {
1183 key: PropName::Str(key.as_str().into()),
1184 value: Box::new(value_expr),
1185 };
1186
1187 glob_map
1188 .props
1189 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(prop))));
1190 }
1191
1192 let expr = quote_expr!(
1193 "$turbopack_export_value($obj);",
1194 turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into(),
1195 obj: Expr = Expr::Object(glob_map),
1196 );
1197
1198 let module = ast::Module {
1199 span: DUMMY_SP,
1200 body: vec![ModuleItem::Stmt(Stmt::Expr(ExprStmt {
1201 span: DUMMY_SP,
1202 expr,
1203 }))],
1204 shebang: None,
1205 };
1206
1207 let source_map: Arc<swc_core::common::SourceMap> = Default::default();
1208
1209 let mut bytes: Vec<u8> = vec![];
1210 let mut wr: JsWriter<'_, &mut Vec<u8>> =
1211 JsWriter::new(source_map.clone(), "\n", &mut bytes, None);
1212 if matches!(*minify, MinifyType::Minify { .. }) {
1213 wr.set_indent_str("");
1214 }
1215
1216 let mut emitter = Emitter {
1217 cfg: swc_core::ecma::codegen::Config::default(),
1218 cm: source_map.clone(),
1219 comments: None,
1220 wr,
1221 };
1222
1223 emitter.emit_module(&module)?;
1224
1225 Ok(EcmascriptChunkItemContent {
1226 inner_code: bytes.into(),
1227 ..Default::default()
1228 }
1229 .cell())
1230 }
1231}
1232
1233#[turbo_tasks::value]
1238#[derive(Hash, Debug, ValueToString)]
1239pub struct ImportMetaGlobAssetReference {
1240 pub inner: ResolvedVc<ImportMetaGlobAsset>,
1241 pub patterns: Vec<RcStr>,
1242}
1243
1244impl std::fmt::Display for ImportMetaGlobAssetReference {
1245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1246 write!(f, "import.meta.glob {}", self.patterns.join(", "))
1247 }
1248}
1249
1250impl ImportMetaGlobAssetReference {
1251 pub fn new(
1252 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
1253 patterns: Vec<RcStr>,
1254 eager: bool,
1255 lazy_compilation: bool,
1256 import: Option<RcStr>,
1257 query: Option<RcStr>,
1258 base: Option<RcStr>,
1259 case_sensitive: bool,
1260 issue_source: Option<IssueSource>,
1261 error_mode: ResolveErrorMode,
1262 ) -> Self {
1263 let inner = ImportMetaGlobAsset {
1264 origin,
1265 patterns: patterns.clone(),
1266 eager,
1267 lazy_compilation: lazy_compilation && !eager,
1268 import,
1269 query,
1270 base,
1271 case_sensitive,
1272 issue_source,
1273 error_mode,
1274 }
1275 .resolved_cell();
1276
1277 ImportMetaGlobAssetReference { inner, patterns }
1278 }
1279}
1280
1281#[turbo_tasks::value_impl]
1282impl ModuleReference for ImportMetaGlobAssetReference {
1283 #[turbo_tasks::function]
1284 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
1285 *ModuleResolveResult::module(ResolvedVc::upcast(self.inner))
1286 }
1287
1288 fn chunking_type(&self) -> Option<ChunkingType> {
1289 Some(ChunkingType::Parallel {
1290 inherit_async: false,
1291 hoisted: false,
1292 })
1293 }
1294}
1295
1296impl IntoCodeGenReference for ImportMetaGlobAssetReference {
1297 fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
1298 ResolvedVc::upcast(self.resolved_cell())
1299 }
1300
1301 fn into_code_gen_reference(
1302 self,
1303 _trie: &AstPathTrieBuilder,
1304 path: AstPathId,
1305 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
1306 let reference = self.resolved_cell();
1307 (
1308 ResolvedVc::upcast(reference),
1309 CodeGen::ImportMetaGlobAssetReferenceCodeGen(ImportMetaGlobAssetReferenceCodeGen {
1310 reference,
1311 path,
1312 }),
1313 )
1314 }
1315}
1316
1317#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
1322pub struct ImportMetaGlobAssetReferenceCodeGen {
1323 path: AstPathId,
1324 reference: ResolvedVc<ImportMetaGlobAssetReference>,
1325}
1326
1327impl ImportMetaGlobAssetReferenceCodeGen {
1328 pub async fn code_generation(
1329 &self,
1330 trie: &AstPathTrie,
1331 chunking_context: Vc<Box<dyn ChunkingContext>>,
1332 ) -> Result<CodeGeneration> {
1333 let module_id = self
1334 .reference
1335 .await?
1336 .inner
1337 .chunk_item_id(chunking_context)
1338 .await?;
1339
1340 let mut visitors = Vec::new();
1341 visitors.push(create_visitor!(
1342 trie,
1343 self.path,
1344 visit_mut_expr,
1345 |expr: &mut Expr| {
1346 if let Expr::Call(_) = expr {
1347 *expr = quote!(
1349 "$turbopack_require($id)" as Expr,
1350 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
1351 id: Expr = module_id_to_lit(&module_id)
1352 );
1353 }
1354 }
1355 ));
1356 Ok(CodeGeneration::visitors(visitors))
1357 }
1358}