1pub(crate) mod custom_module_type;
2pub mod match_mode;
3pub mod module_options_context;
4pub mod module_rule;
5pub mod rule_condition;
6pub mod transition_rule;
7
8use anyhow::{Context, Result};
9pub use custom_module_type::CustomModuleType;
10pub use module_options_context::*;
11pub use module_rule::*;
12pub use rule_condition::*;
13use turbo_rcstr::RcStr;
14use turbo_tasks::{ResolvedVc, Vc};
15use turbo_tasks_fs::{FileSystemPath, glob::Glob};
16use turbopack_core::{
17 chunk::SourceMapsType,
18 reference_type::{CssReferenceSubType, ReferenceType, UrlReferenceSubType},
19 resolve::options::{ImportMap, ImportMapping},
20};
21use turbopack_css::CssModuleAssetType;
22use turbopack_ecmascript::{
23 EcmascriptInputTransform, EcmascriptInputTransforms, EcmascriptOptions, SpecifiedModuleType,
24};
25use turbopack_mdx::MdxTransform;
26use turbopack_node::transforms::{postcss::PostCssTransform, webpack::WebpackLoaders};
27use turbopack_wasm::source::WebAssemblySourceType;
28
29use crate::{
30 evaluate_context::node_evaluate_asset_context, resolve_options_context::ResolveOptionsContext,
31};
32
33#[turbo_tasks::function]
34async fn package_import_map_from_import_mapping(
35 package_name: RcStr,
36 package_mapping: ResolvedVc<ImportMapping>,
37) -> Vc<ImportMap> {
38 let mut import_map = ImportMap::default();
39 import_map.insert_exact_alias(format!("@vercel/turbopack/{package_name}"), package_mapping);
40 import_map.cell()
41}
42
43#[turbo_tasks::function]
44async fn package_import_map_from_context(
45 package_name: RcStr,
46 context_path: ResolvedVc<FileSystemPath>,
47) -> Vc<ImportMap> {
48 let mut import_map = ImportMap::default();
49 import_map.insert_exact_alias(
50 format!("@vercel/turbopack/{package_name}"),
51 ImportMapping::PrimaryAlternative(package_name, Some(context_path)).resolved_cell(),
52 );
53 import_map.cell()
54}
55
56#[turbo_tasks::value(cell = "new", eq = "manual")]
57pub struct ModuleOptions {
58 pub rules: Vec<ModuleRule>,
59}
60
61#[turbo_tasks::value_impl]
62impl ModuleOptions {
63 #[turbo_tasks::function]
64 pub async fn new(
65 path: Vc<FileSystemPath>,
66 module_options_context: Vc<ModuleOptionsContext>,
67 resolve_options_context: Vc<ResolveOptionsContext>,
68 ) -> Result<Vc<ModuleOptions>> {
69 let ModuleOptionsContext {
70 css: CssOptionsContext { enable_raw_css, .. },
71 ref enable_postcss_transform,
72 ref enable_webpack_loaders,
73 ref rules,
74 ..
75 } = *module_options_context.await?;
76
77 if !rules.is_empty() {
78 let path_value = path.await?;
79
80 for (condition, new_context) in rules.iter() {
81 if condition.matches(&path_value).await? {
82 return Ok(ModuleOptions::new(
83 path,
84 **new_context,
85 resolve_options_context,
86 ));
87 }
88 }
89 }
90
91 let need_path = (!enable_raw_css
92 && if let Some(options) = enable_postcss_transform {
93 let options = options.await?;
94 options.postcss_package.is_none()
95 } else {
96 false
97 })
98 || if let Some(options) = enable_webpack_loaders {
99 let options = options.await?;
100 options.loader_runner_package.is_none()
101 } else {
102 false
103 };
104
105 Ok(Self::new_internal(
106 need_path.then_some(path),
107 module_options_context,
108 resolve_options_context,
109 ))
110 }
111
112 #[turbo_tasks::function]
113 async fn new_internal(
114 path: Option<Vc<FileSystemPath>>,
115 module_options_context: Vc<ModuleOptionsContext>,
116 resolve_options_context: Vc<ResolveOptionsContext>,
117 ) -> Result<Vc<ModuleOptions>> {
118 let ModuleOptionsContext {
119 ecmascript:
120 EcmascriptOptionsContext {
121 enable_jsx,
122 enable_types,
123 ref enable_typescript_transform,
124 ref enable_decorators,
125 ignore_dynamic_requests,
126 import_externals,
127 esm_url_rewrite_behavior,
128 ref enable_typeof_window_inlining,
129 source_maps: ecmascript_source_maps,
130 ..
131 },
132 enable_mdx,
133 enable_mdx_rs,
134 css:
135 CssOptionsContext {
136 enable_raw_css,
137 source_maps: css_source_maps,
138 ..
139 },
140 ref enable_postcss_transform,
141 ref enable_webpack_loaders,
142 preset_env_versions,
143 ref module_rules,
144 execution_context,
145 tree_shaking_mode,
146 keep_last_successful_parse,
147 ..
148 } = *module_options_context.await?;
149
150 let mut refresh = false;
151 let mut transforms = vec![];
152
153 if let Some(enable_jsx) = enable_jsx {
158 let jsx = enable_jsx.await?;
159 refresh = jsx.react_refresh;
160
161 transforms.push(EcmascriptInputTransform::React {
162 development: jsx.development,
163 refresh: jsx.react_refresh,
164 import_source: ResolvedVc::cell(jsx.import_source.clone()),
165 runtime: ResolvedVc::cell(jsx.runtime.clone()),
166 });
167 }
168
169 let ecmascript_options = EcmascriptOptions {
170 tree_shaking_mode,
171 url_rewrite_behavior: esm_url_rewrite_behavior,
172 import_externals,
173 ignore_dynamic_requests,
174 refresh,
175 extract_source_map: matches!(ecmascript_source_maps, SourceMapsType::Full),
176 keep_last_successful_parse,
177 ..Default::default()
178 };
179 let ecmascript_options_vc = ecmascript_options.resolved_cell();
180
181 if let Some(env) = preset_env_versions {
182 transforms.push(EcmascriptInputTransform::PresetEnv(env));
183 }
184
185 if let Some(enable_typeof_window_inlining) = enable_typeof_window_inlining {
186 transforms.push(EcmascriptInputTransform::GlobalTypeofs {
187 window_value: match enable_typeof_window_inlining {
188 TypeofWindow::Object => "object".to_string(),
189 TypeofWindow::Undefined => "undefined".to_string(),
190 },
191 });
192 }
193
194 let ts_transform = if let Some(options) = enable_typescript_transform {
195 let options = options.await?;
196 Some(EcmascriptInputTransform::TypeScript {
197 use_define_for_class_fields: options.use_define_for_class_fields,
198 })
199 } else {
200 None
201 };
202
203 let decorators_transform = if let Some(options) = &enable_decorators {
204 let options = options.await?;
205 options
206 .decorators_kind
207 .as_ref()
208 .map(|kind| EcmascriptInputTransform::Decorators {
209 is_legacy: kind == &DecoratorsKind::Legacy,
210 is_ecma: kind == &DecoratorsKind::Ecma,
211 emit_decorators_metadata: options.emit_decorators_metadata,
212 use_define_for_class_fields: options.use_define_for_class_fields,
213 })
214 } else {
215 None
216 };
217
218 let vendor_transforms = Vc::<EcmascriptInputTransforms>::cell(vec![]);
219 let ts_app_transforms = if let Some(transform) = &ts_transform {
220 let base_transforms = if let Some(decorators_transform) = &decorators_transform {
221 vec![decorators_transform.clone(), transform.clone()]
222 } else {
223 vec![transform.clone()]
224 };
225 Vc::<EcmascriptInputTransforms>::cell(
226 base_transforms
227 .iter()
228 .cloned()
229 .chain(transforms.iter().cloned())
230 .collect(),
231 )
232 } else {
233 Vc::cell(transforms.clone())
234 };
235
236 let app_transforms = Vc::<EcmascriptInputTransforms>::cell(
245 if let Some(decorators_transform) = &decorators_transform {
246 vec![decorators_transform.clone()]
247 } else {
248 vec![]
249 }
250 .iter()
251 .cloned()
252 .chain(transforms.iter().cloned())
253 .collect(),
254 );
255
256 let mut rules = vec![
257 ModuleRule::new_all(
258 RuleCondition::any(vec![
259 RuleCondition::ResourcePathEndsWith(".json".to_string()),
260 RuleCondition::ContentTypeStartsWith("application/json".to_string()),
261 ]),
262 vec![ModuleRuleEffect::ModuleType(ModuleType::Json)],
263 ),
264 ModuleRule::new_all(
265 RuleCondition::any(vec![
266 RuleCondition::ResourcePathEndsWith(".js".to_string()),
267 RuleCondition::ResourcePathEndsWith(".jsx".to_string()),
268 RuleCondition::ContentTypeStartsWith("application/javascript".to_string()),
269 RuleCondition::ContentTypeStartsWith("text/javascript".to_string()),
270 ]),
271 vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
272 transforms: app_transforms.to_resolved().await?,
273 options: ecmascript_options_vc,
274 })],
275 ),
276 ModuleRule::new_all(
277 RuleCondition::ResourcePathEndsWith(".mjs".to_string()),
278 vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
279 transforms: app_transforms.to_resolved().await?,
280 options: EcmascriptOptions {
281 specified_module_type: SpecifiedModuleType::EcmaScript,
282 ..ecmascript_options
283 }
284 .resolved_cell(),
285 })],
286 ),
287 ModuleRule::new_all(
288 RuleCondition::ResourcePathEndsWith(".cjs".to_string()),
289 vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
290 transforms: app_transforms.to_resolved().await?,
291 options: EcmascriptOptions {
292 specified_module_type: SpecifiedModuleType::CommonJs,
293 ..ecmascript_options
294 }
295 .resolved_cell(),
296 })],
297 ),
298 ModuleRule::new_all(
299 RuleCondition::ResourcePathEndsWith(".ts".to_string()),
300 vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
301 transforms: ts_app_transforms.to_resolved().await?,
302 tsx: false,
303 analyze_types: enable_types,
304 options: ecmascript_options_vc,
305 })],
306 ),
307 ModuleRule::new_all(
308 RuleCondition::ResourcePathEndsWith(".tsx".to_string()),
309 vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
310 transforms: ts_app_transforms.to_resolved().await?,
311 tsx: true,
312 analyze_types: enable_types,
313 options: ecmascript_options_vc,
314 })],
315 ),
316 ModuleRule::new_all(
317 RuleCondition::ResourcePathEndsWith(".mts".to_string()),
318 vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
319 transforms: ts_app_transforms.to_resolved().await?,
320 tsx: false,
321 analyze_types: enable_types,
322 options: EcmascriptOptions {
323 specified_module_type: SpecifiedModuleType::EcmaScript,
324 ..ecmascript_options
325 }
326 .resolved_cell(),
327 })],
328 ),
329 ModuleRule::new_all(
330 RuleCondition::ResourcePathEndsWith(".mtsx".to_string()),
331 vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
332 transforms: ts_app_transforms.to_resolved().await?,
333 tsx: true,
334 analyze_types: enable_types,
335 options: EcmascriptOptions {
336 specified_module_type: SpecifiedModuleType::EcmaScript,
337 ..ecmascript_options
338 }
339 .resolved_cell(),
340 })],
341 ),
342 ModuleRule::new_all(
343 RuleCondition::ResourcePathEndsWith(".cts".to_string()),
344 vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
345 transforms: ts_app_transforms.to_resolved().await?,
346 tsx: false,
347 analyze_types: enable_types,
348 options: EcmascriptOptions {
349 specified_module_type: SpecifiedModuleType::CommonJs,
350 ..ecmascript_options
351 }
352 .resolved_cell(),
353 })],
354 ),
355 ModuleRule::new_all(
356 RuleCondition::ResourcePathEndsWith(".ctsx".to_string()),
357 vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
358 transforms: ts_app_transforms.to_resolved().await?,
359 tsx: true,
360 analyze_types: enable_types,
361 options: EcmascriptOptions {
362 specified_module_type: SpecifiedModuleType::CommonJs,
363 ..ecmascript_options
364 }
365 .resolved_cell(),
366 })],
367 ),
368 ModuleRule::new(
369 RuleCondition::ResourcePathEndsWith(".d.ts".to_string()),
370 vec![ModuleRuleEffect::ModuleType(
371 ModuleType::TypescriptDeclaration {
372 transforms: vendor_transforms.to_resolved().await?,
373 options: ecmascript_options_vc,
374 },
375 )],
376 ),
377 ModuleRule::new(
378 RuleCondition::any(vec![RuleCondition::ResourcePathEndsWith(
379 ".node".to_string(),
380 )]),
381 vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)],
382 ),
383 ModuleRule::new(
385 RuleCondition::any(vec![
386 RuleCondition::ResourcePathEndsWith(".wasm".to_string()),
387 RuleCondition::ContentTypeStartsWith("application/wasm".to_string()),
388 ]),
389 vec![ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
390 source_ty: WebAssemblySourceType::Binary,
391 })],
392 ),
393 ModuleRule::new(
394 RuleCondition::any(vec![RuleCondition::ResourcePathEndsWith(
395 ".wat".to_string(),
396 )]),
397 vec![ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
398 source_ty: WebAssemblySourceType::Text,
399 })],
400 ),
401 ModuleRule::new(
403 RuleCondition::all(vec![
404 RuleCondition::ResourcePathHasNoExtension,
405 RuleCondition::ContentTypeEmpty,
406 ]),
407 vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
408 transforms: vendor_transforms.to_resolved().await?,
409 options: ecmascript_options_vc,
410 })],
411 ),
412 ModuleRule::new(
414 RuleCondition::any(vec![
415 RuleCondition::ResourcePathEndsWith(".apng".to_string()),
416 RuleCondition::ResourcePathEndsWith(".avif".to_string()),
417 RuleCondition::ResourcePathEndsWith(".gif".to_string()),
418 RuleCondition::ResourcePathEndsWith(".ico".to_string()),
419 RuleCondition::ResourcePathEndsWith(".jpg".to_string()),
420 RuleCondition::ResourcePathEndsWith(".jpeg".to_string()),
421 RuleCondition::ResourcePathEndsWith(".png".to_string()),
422 RuleCondition::ResourcePathEndsWith(".svg".to_string()),
423 RuleCondition::ResourcePathEndsWith(".webp".to_string()),
424 RuleCondition::ResourcePathEndsWith(".woff2".to_string()),
425 ]),
426 vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs)],
427 ),
428 ModuleRule::new(
429 RuleCondition::ReferenceType(ReferenceType::Url(UrlReferenceSubType::Undefined)),
430 vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs)],
431 ),
432 ModuleRule::new(
433 RuleCondition::ReferenceType(ReferenceType::Url(UrlReferenceSubType::CssUrl)),
434 vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlCss)],
435 ),
436 ];
437
438 if enable_raw_css {
439 rules.extend([
440 ModuleRule::new(
441 RuleCondition::any(vec![
442 RuleCondition::ResourcePathEndsWith(".css".to_string()),
443 RuleCondition::ContentTypeStartsWith("text/css".to_string()),
444 ]),
445 vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
446 ty: CssModuleAssetType::Default,
447 })],
448 ),
449 ModuleRule::new(
450 RuleCondition::any(vec![
451 RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
452 RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
453 ]),
454 vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
455 ty: CssModuleAssetType::Module,
456 })],
457 ),
458 ]);
459 } else {
460 if let Some(options) = enable_postcss_transform {
461 let options = options.await?;
462 let execution_context = execution_context
463 .context("execution_context is required for the postcss_transform")?;
464
465 let import_map = if let Some(postcss_package) = options.postcss_package {
466 package_import_map_from_import_mapping("postcss".into(), *postcss_package)
467 } else {
468 package_import_map_from_context(
469 "postcss".into(),
470 path.context("need_path in ModuleOptions::new is incorrect")?,
471 )
472 };
473
474 rules.push(ModuleRule::new(
475 RuleCondition::Any(vec![
476 RuleCondition::ResourcePathEndsWith(".css".to_string()),
477 RuleCondition::ContentTypeStartsWith("text/css".to_string()),
478 ]),
479 vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
480 ResolvedVc::upcast(
481 PostCssTransform::new(
482 node_evaluate_asset_context(
483 *execution_context,
484 Some(import_map),
485 None,
486 "postcss".into(),
487 true,
488 ),
489 *execution_context,
490 options.config_location,
491 matches!(css_source_maps, SourceMapsType::Full),
492 )
493 .to_resolved()
494 .await?,
495 ),
496 ]))],
497 ));
498 }
499
500 rules.extend([
501 ModuleRule::new_all(
502 RuleCondition::Any(vec![
503 RuleCondition::ResourcePathEndsWith(".css".to_string()),
504 RuleCondition::ContentTypeStartsWith("text/css".to_string()),
505 ]),
506 vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
507 ty: CssModuleAssetType::Default,
508 })],
509 ),
510 ModuleRule::new(
511 RuleCondition::all(vec![
512 RuleCondition::Any(vec![
513 RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
514 RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
515 ]),
516 RuleCondition::not(RuleCondition::ReferenceType(ReferenceType::Css(
520 CssReferenceSubType::AtImport(None),
521 ))),
522 ]),
523 vec![ModuleRuleEffect::ModuleType(ModuleType::CssModule)],
524 ),
525 ModuleRule::new(
526 RuleCondition::all(vec![
527 RuleCondition::Any(vec![
528 RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
529 RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
530 ]),
531 RuleCondition::ReferenceType(ReferenceType::Css(
533 CssReferenceSubType::AtImport(None),
534 )),
535 ]),
536 vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
537 ty: CssModuleAssetType::Module,
538 })],
539 ),
540 ModuleRule::new_internal(
542 RuleCondition::Any(vec![
543 RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
544 RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
545 ]),
546 vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
547 ty: CssModuleAssetType::Module,
548 })],
549 ),
550 ModuleRule::new(
552 RuleCondition::all(vec![
553 RuleCondition::ReferenceType(ReferenceType::Css(
554 CssReferenceSubType::Analyze,
555 )),
556 RuleCondition::Any(vec![
557 RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
558 RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
559 ]),
560 ]),
561 vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
562 ty: CssModuleAssetType::Module,
563 })],
564 ),
565 ]);
566 }
567
568 if enable_mdx || enable_mdx_rs.is_some() {
569 let (jsx_runtime, jsx_import_source, development) = if let Some(enable_jsx) = enable_jsx
570 {
571 let jsx = enable_jsx.await?;
572 (
573 jsx.runtime.clone(),
574 jsx.import_source.clone(),
575 jsx.development,
576 )
577 } else {
578 (None, None, false)
579 };
580
581 let mdx_options = &*enable_mdx_rs
582 .unwrap_or_else(|| MdxTransformOptions::default().resolved_cell())
583 .await?;
584
585 let mdx_transform_options = (MdxTransformOptions {
586 development: Some(development),
587 jsx: Some(false),
588 jsx_runtime,
589 jsx_import_source,
590 ..(mdx_options.clone())
591 })
592 .cell();
593
594 rules.push(ModuleRule::new(
595 RuleCondition::any(vec![
596 RuleCondition::ResourcePathEndsWith(".md".to_string()),
597 RuleCondition::ResourcePathEndsWith(".mdx".to_string()),
598 RuleCondition::ContentTypeStartsWith("text/markdown".to_string()),
599 ]),
600 vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
601 ResolvedVc::upcast(
602 MdxTransform::new(mdx_transform_options)
603 .to_resolved()
604 .await?,
605 ),
606 ]))],
607 ));
608 }
609
610 if let Some(webpack_loaders_options) = enable_webpack_loaders {
611 let webpack_loaders_options = webpack_loaders_options.await?;
612 let execution_context =
613 execution_context.context("execution_context is required for webpack_loaders")?;
614 let import_map = if let Some(loader_runner_package) =
615 webpack_loaders_options.loader_runner_package
616 {
617 package_import_map_from_import_mapping(
618 "loader-runner".into(),
619 *loader_runner_package,
620 )
621 } else {
622 package_import_map_from_context(
623 "loader-runner".into(),
624 path.context("need_path in ModuleOptions::new is incorrect")?,
625 )
626 };
627 for (key, rule) in webpack_loaders_options.rules.await?.iter() {
628 rules.push(ModuleRule::new(
629 RuleCondition::All(vec![
630 if key.starts_with("#") {
631 let conditions = (*webpack_loaders_options.conditions.await?)
633 .context(
634 "Expected a condition entry for the webpack loader rule \
635 matching {key}. Create a `conditions` mapping in your \
636 next.config.js",
637 )?
638 .await?;
639
640 let condition = conditions.get(key).context(
641 "Expected a condition entry for the webpack loader rule matching \
642 {key}.",
643 )?;
644
645 match &condition.path {
646 ConditionPath::Glob(glob) => RuleCondition::ResourcePathGlob {
647 base: execution_context.project_path().await?,
648 glob: Glob::new(glob.clone()).await?,
649 },
650 ConditionPath::Regex(regex) => {
651 RuleCondition::ResourcePathEsRegex(regex.await?)
652 }
653 }
654 } else if key.contains('/') {
655 RuleCondition::ResourcePathGlob {
656 base: execution_context.project_path().await?,
657 glob: Glob::new(key.clone()).await?,
658 }
659 } else {
660 RuleCondition::ResourceBasePathGlob(Glob::new(key.clone()).await?)
661 },
662 RuleCondition::not(RuleCondition::ResourceIsVirtualSource),
663 ]),
664 vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
665 ResolvedVc::upcast(
666 WebpackLoaders::new(
667 node_evaluate_asset_context(
668 *execution_context,
669 Some(import_map),
670 None,
671 "webpack_loaders".into(),
672 false,
673 ),
674 *execution_context,
675 *rule.loaders,
676 rule.rename_as.clone(),
677 resolve_options_context,
678 matches!(ecmascript_source_maps, SourceMapsType::Full),
679 )
680 .to_resolved()
681 .await?,
682 ),
683 ]))],
684 ));
685 }
686 }
687
688 rules.extend(module_rules.iter().cloned());
689
690 Ok(ModuleOptions::cell(ModuleOptions { rules }))
691 }
692}