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