use anyhow::{Context, Result, bail};
use async_trait::async_trait;
use bincode::{Decode, Encode};
use either::Either;
use rustc_hash::FxHashSet;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value as JsonValue;
use turbo_esregex::EsRegex;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
trace::TraceRawVcs,
};
use turbo_tasks_env::EnvMap;
use turbo_tasks_fetch::FetchClientConfig;
use turbo_tasks_fs::{
FileSystemPath,
glob::{Glob, GlobOptions},
};
use turbopack::module_options::{
ConditionContentType, ConditionItem, ConditionPath, ConditionQuery, LoaderRuleItem,
WebpackRules, module_options_context::MdxTransformOptions,
};
use turbopack_core::{
chunk::{CrossOrigin, SourceMapsType},
issue::{
IgnoreIssue, IgnoreIssuePattern, Issue, IssueExt, IssueSeverity, IssueStage, StyledString,
},
resolve::ResolveAliasMap,
};
use turbopack_ecmascript::{OptionTreeShaking, TreeShakingMode};
use turbopack_ecmascript_plugins::transform::{
emotion::EmotionTransformConfig, relay::RelayConfig,
styled_components::StyledComponentsTransformConfig,
};
use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
use crate::{
app_structure::FileSystemPathVec,
mode::NextMode,
next_import_map::mdx_import_source_file,
next_shared::{
transforms::ModularizeImportPackageConfig, webpack_rules::WebpackLoaderBuiltinCondition,
},
};
#[turbo_tasks::value(transparent)]
pub struct ModularizeImports(
#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<String, ModularizeImportPackageConfig>,
);
#[turbo_tasks::value(transparent)]
#[derive(Clone, Debug)]
pub struct CacheKinds(FxHashSet<RcStr>);
impl CacheKinds {
pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
self.0.extend(iter);
}
}
impl Default for CacheKinds {
fn default() -> Self {
CacheKinds(
["default", "remote", "private"]
.iter()
.map(|&s| s.into())
.collect(),
)
}
}
#[turbo_tasks::value(transparent)]
pub struct CacheHandlersMap(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, RcStr>);
#[turbo_tasks::value(eq = "manual")]
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct NextConfig {
// IMPORTANT: all fields should be private and access should be wrapped within a turbo-tasks
// function. Otherwise changing NextConfig will lead to invalidating all tasks accessing it.
config_file: Option<RcStr>,
config_file_name: RcStr,
/// In-memory cache size in bytes.
///
/// If `cache_max_memory_size: 0` disables in-memory caching.
cache_max_memory_size: Option<f64>,
/// custom path to a cache handler to use
cache_handler: Option<RcStr>,
#[bincode(with_serde)]
cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
env: FxIndexMap<String, JsonValue>,
experimental: ExperimentalConfig,
images: ImageConfig,
page_extensions: Vec<RcStr>,
react_compiler: Option<ReactCompilerOptionsOrBoolean>,
react_production_profiling: Option<bool>,
react_strict_mode: Option<bool>,
transpile_packages: Option<Vec<RcStr>>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
dist_dir: RcStr,
dist_dir_root: RcStr,
deployment_id: Option<RcStr>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
sass_options: Option<serde_json::Value>,
trailing_slash: Option<bool>,
asset_prefix: Option<RcStr>,
base_path: Option<RcStr>,
skip_proxy_url_normalize: Option<bool>,
skip_trailing_slash_redirect: Option<bool>,
i18n: Option<I18NConfig>,
cross_origin: CrossOrigin,
dev_indicators: Option<DevIndicatorsConfig>,
output: Option<OutputType>,
turbopack: Option<TurbopackConfig>,
production_browser_source_maps: bool,
#[bincode(with = "turbo_bincode::serde_self_describing")]
output_file_tracing_includes: Option<serde_json::Value>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
output_file_tracing_excludes: Option<serde_json::Value>,
// TODO: This option is not respected, it uses Turbopack's root instead.
output_file_tracing_root: Option<RcStr>,
/// Enables the bundling of node_modules packages (externals) for pages
/// server-side bundles.
///
/// [API Reference](https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies)
bundle_pages_router_dependencies: Option<bool>,
/// A list of packages that should be treated as external on the server
/// build.
///
/// [API Reference](https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages)
server_external_packages: Option<Vec<RcStr>>,
#[serde(rename = "_originalRedirects")]
original_redirects: Option<Vec<Redirect>>,
// Partially supported
compiler: Option<CompilerConfig>,
optimize_fonts: Option<bool>,
clean_dist_dir: bool,
compress: bool,
eslint: EslintConfig,
exclude_default_moment_locales: bool,
generate_etags: bool,
http_agent_options: HttpAgentConfig,
on_demand_entries: OnDemandEntriesConfig,
powered_by_header: bool,
#[bincode(with = "turbo_bincode::serde_self_describing")]
public_runtime_config: FxIndexMap<String, serde_json::Value>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
server_runtime_config: FxIndexMap<String, serde_json::Value>,
static_page_generation_timeout: f64,
target: Option<String>,
typescript: TypeScriptConfig,
use_file_system_public_routes: bool,
cache_components: Option<bool>,
//
// These are never used by Turbopack, and potentially non-serializable anyway:
// cache_life: (),
// export_path_map: Option<serde_json::Value>,
// generate_build_id: Option<serde_json::Value>,
// webpack: Option<serde_json::Value>,
}
#[turbo_tasks::value_impl]
impl NextConfig {
#[turbo_tasks::function]
pub fn with_analyze_config(&self) -> Vc<Self> {
let mut new = self.clone();
new.experimental.turbopack_source_maps = Some(true);
new.experimental.turbopack_input_source_maps = Some(false);
new.cell()
}
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
struct EslintConfig {
dirs: Option<Vec<String>>,
ignore_during_builds: Option<bool>,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum BuildActivityPositions {
#[default]
BottomRight,
BottomLeft,
TopRight,
TopLeft,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct DevIndicatorsOptions {
pub build_activity_position: Option<BuildActivityPositions>,
pub position: Option<BuildActivityPositions>,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum DevIndicatorsConfig {
WithOptions(DevIndicatorsOptions),
Boolean(bool),
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
struct OnDemandEntriesConfig {
max_inactive_age: f64,
pages_buffer_length: f64,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
struct HttpAgentConfig {
keep_alive: bool,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct DomainLocale {
pub default_locale: String,
pub domain: String,
pub http: Option<bool>,
pub locales: Option<Vec<String>>,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct I18NConfig {
pub default_locale: String,
pub domains: Option<Vec<DomainLocale>>,
pub locale_detection: Option<bool>,
pub locales: Vec<String>,
}
#[turbo_tasks::value(transparent)]
pub struct OptionI18NConfig(Option<I18NConfig>);
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum OutputType {
Standalone,
Export,
}
#[turbo_tasks::value(transparent)]
pub struct OptionOutputType(Option<OutputType>);
#[derive(
Debug,
Clone,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
TaskInput,
TraceRawVcs,
Serialize,
Deserialize,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum RouteHas {
Header {
key: RcStr,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<RcStr>,
},
Cookie {
key: RcStr,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<RcStr>,
},
Query {
key: RcStr,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<RcStr>,
},
Host {
value: RcStr,
},
}
#[derive(Clone, Debug, Default, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
#[serde(rename_all = "camelCase")]
pub struct HeaderValue {
pub key: RcStr,
pub value: RcStr,
}
#[derive(Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
#[serde(rename_all = "camelCase")]
pub struct Header {
pub source: String,
pub base_path: Option<bool>,
pub locale: Option<bool>,
pub headers: Vec<HeaderValue>,
pub has: Option<Vec<RouteHas>>,
pub missing: Option<Vec<RouteHas>>,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(rename_all = "camelCase")]
pub enum RedirectStatus {
StatusCode(f64),
Permanent(bool),
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct Redirect {
pub source: String,
pub destination: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_path: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has: Option<Vec<RouteHas>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub missing: Option<Vec<RouteHas>>,
#[serde(flatten)]
pub status: RedirectStatus,
}
#[derive(Clone, Debug)]
pub struct Rewrite {
pub source: String,
pub destination: String,
pub base_path: Option<bool>,
pub locale: Option<bool>,
pub has: Option<Vec<RouteHas>>,
pub missing: Option<Vec<RouteHas>>,
}
#[derive(Clone, Debug)]
pub struct Rewrites {
pub before_files: Vec<Rewrite>,
pub after_files: Vec<Rewrite>,
pub fallback: Vec<Rewrite>,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct TypeScriptConfig {
pub ignore_build_errors: Option<bool>,
pub tsconfig_path: Option<String>,
}
#[turbo_tasks::value(eq = "manual", operation)]
#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageConfig {
pub device_sizes: Vec<u16>,
pub image_sizes: Vec<u16>,
pub path: String,
pub loader: ImageLoader,
#[serde(deserialize_with = "empty_string_is_none")]
pub loader_file: Option<String>,
pub domains: Vec<String>,
pub disable_static_images: bool,
#[serde(rename = "minimumCacheTTL")]
pub minimum_cache_ttl: u64,
pub formats: Vec<ImageFormat>,
#[serde(rename = "dangerouslyAllowSVG")]
pub dangerously_allow_svg: bool,
pub content_security_policy: String,
pub remote_patterns: Vec<RemotePattern>,
pub unoptimized: bool,
}
fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let o = Option::<String>::deserialize(deserializer)?;
Ok(o.filter(|s| !s.is_empty()))
}
impl Default for ImageConfig {
fn default() -> Self {
// https://github.com/vercel/next.js/blob/327634eb/packages/next/shared/lib/image-config.ts#L100-L114
Self {
device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
image_sizes: vec![32, 48, 64, 96, 128, 256, 384],
path: "/_next/image".to_string(),
loader: ImageLoader::Default,
loader_file: None,
domains: vec![],
disable_static_images: false,
minimum_cache_ttl: 60,
formats: vec![ImageFormat::Webp],
dangerously_allow_svg: false,
content_security_policy: "".to_string(),
remote_patterns: vec![],
unoptimized: false,
}
}
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum ImageLoader {
Default,
Imgix,
Cloudinary,
Akamai,
Custom,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
pub enum ImageFormat {
#[serde(rename = "image/webp")]
Webp,
#[serde(rename = "image/avif")]
Avif,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct RemotePattern {
pub hostname: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<RemotePatternProtocol>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pathname: Option<String>,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum RemotePatternProtocol {
Http,
Https,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct TurbopackConfig {
#[serde(default)]
#[bincode(with = "turbo_bincode::indexmap")]
pub rules: FxIndexMap<RcStr, RuleConfigCollection>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
pub resolve_extensions: Option<Vec<RcStr>>,
pub debug_ids: Option<bool>,
/// Issue patterns to ignore (suppress) from Turbopack output.
#[serde(default)]
pub ignore_issue: Option<Vec<TurbopackIgnoreIssueRule>>,
}
#[derive(
Deserialize,
Clone,
PartialEq,
Eq,
Debug,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(deny_unknown_fields)]
pub struct RegexComponents {
source: RcStr,
flags: RcStr,
}
/// This type should not be hand-written, but instead `packages/next/src/build/swc/index.ts` will
/// transform a JS `RegExp` to a `RegexComponents` or a string to a `Glob` before passing it to us.
///
/// This is needed because `RegExp` objects are not otherwise serializable.
#[derive(
Clone,
PartialEq,
Eq,
Debug,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(
tag = "type",
content = "value",
rename_all = "camelCase",
deny_unknown_fields
)]
pub enum ConfigConditionPath {
Glob(RcStr),
Regex(RegexComponents),
}
impl TryFrom<ConfigConditionPath> for ConditionPath {
type Error = anyhow::Error;
fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
Ok(match config {
ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
ConfigConditionPath::Regex(path) => {
ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
}
})
}
}
impl TryFrom<RegexComponents> for EsRegex {
type Error = anyhow::Error;
fn try_from(components: RegexComponents) -> Result<EsRegex> {
EsRegex::new(&components.source, &components.flags)
}
}
#[derive(
Clone,
PartialEq,
Eq,
Debug,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(
tag = "type",
content = "value",
rename_all = "camelCase",
deny_unknown_fields
)]
pub enum ConfigConditionQuery {
Constant(RcStr),
Regex(RegexComponents),
}
impl TryFrom<ConfigConditionQuery> for ConditionQuery {
type Error = anyhow::Error;
fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
Ok(match config {
ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
ConfigConditionQuery::Regex(regex) => {
ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
}
})
}
}
#[derive(
Clone,
PartialEq,
Eq,
Debug,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(
tag = "type",
content = "value",
rename_all = "camelCase",
deny_unknown_fields
)]
pub enum ConfigConditionContentType {
Glob(RcStr),
Regex(RegexComponents),
}
impl TryFrom<ConfigConditionContentType> for ConditionContentType {
type Error = anyhow::Error;
fn try_from(config: ConfigConditionContentType) -> Result<ConditionContentType> {
Ok(match config {
ConfigConditionContentType::Glob(value) => ConditionContentType::Glob(value),
ConfigConditionContentType::Regex(regex) => {
ConditionContentType::Regex(EsRegex::try_from(regex)?.resolved_cell())
}
})
}
}
#[derive(
Deserialize,
Clone,
PartialEq,
Eq,
Debug,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
// We can end up with confusing behaviors if we silently ignore extra properties, since `Base` will
// match nearly every object, since it has no required field.
#[serde(deny_unknown_fields)]
pub enum ConfigConditionItem {
#[serde(rename = "all")]
All(Box<[ConfigConditionItem]>),
#[serde(rename = "any")]
Any(Box<[ConfigConditionItem]>),
#[serde(rename = "not")]
Not(Box<ConfigConditionItem>),
#[serde(untagged)]
Builtin(WebpackLoaderBuiltinCondition),
#[serde(untagged)]
Base {
#[serde(default)]
path: Option<ConfigConditionPath>,
#[serde(default)]
content: Option<RegexComponents>,
#[serde(default)]
query: Option<ConfigConditionQuery>,
#[serde(default, rename = "contentType")]
content_type: Option<ConfigConditionContentType>,
},
}
impl TryFrom<ConfigConditionItem> for ConditionItem {
type Error = anyhow::Error;
fn try_from(config: ConfigConditionItem) -> Result<Self> {
let try_from_vec = |conds: Box<[_]>| {
conds
.into_iter()
.map(ConditionItem::try_from)
.collect::<Result<_>>()
};
Ok(match config {
ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
ConfigConditionItem::Builtin(cond) => {
ConditionItem::Builtin(RcStr::from(cond.as_str()))
}
ConfigConditionItem::Base {
path,
content,
query,
content_type,
} => ConditionItem::Base {
path: path.map(ConditionPath::try_from).transpose()?,
content: content
.map(EsRegex::try_from)
.transpose()?
.map(EsRegex::resolved_cell),
query: query.map(ConditionQuery::try_from).transpose()?,
content_type: content_type
.map(ConditionContentType::try_from)
.transpose()?,
},
})
}
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct RuleConfigItem {
#[serde(default)]
pub loaders: Vec<LoaderItem>,
#[serde(default, alias = "as")]
pub rename_as: Option<RcStr>,
#[serde(default)]
pub condition: Option<ConfigConditionItem>,
#[serde(default, alias = "type")]
pub module_type: Option<RcStr>,
}
#[derive(
Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
impl<'de> Deserialize<'de> for RuleConfigCollection {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
deserializer,
)? {
Either::Left(collection) => Ok(RuleConfigCollection(collection)),
Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
item,
)])),
}
}
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(untagged)]
pub enum RuleConfigCollectionItem {
Shorthand(LoaderItem),
Full(RuleConfigItem),
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(untagged)]
pub enum LoaderItem {
LoaderName(RcStr),
LoaderOptions(WebpackLoaderItem),
}
#[turbo_tasks::value(operation)]
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ModuleIds {
Named,
Deterministic,
}
#[turbo_tasks::value(operation)]
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TurbopackPluginRuntimeStrategy {
#[cfg(feature = "worker_pool")]
WorkerThreads,
#[cfg(feature = "process_pool")]
ChildProcesses,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum MdxRsOptions {
Boolean(bool),
Option(MdxTransformOptions),
}
#[turbo_tasks::value(shared, operation)]
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ReactCompilerCompilationMode {
#[default]
Infer,
Annotation,
All,
}
#[turbo_tasks::value(shared, operation)]
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReactCompilerPanicThreshold {
#[default]
None,
CriticalErrors,
AllErrors,
}
#[turbo_tasks::value(shared, operation)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ReactCompilerTarget {
#[serde(rename = "18")]
React18,
}
/// Subset of react compiler options, we pass these options through to the webpack loader, so it
/// must be serializable
#[turbo_tasks::value(shared, operation)]
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReactCompilerOptions {
#[serde(default)]
pub compilation_mode: ReactCompilerCompilationMode,
#[serde(default)]
pub panic_threshold: ReactCompilerPanicThreshold,
#[serde(default, skip_deserializing, skip_serializing_if = "Option::is_none")]
pub target: Option<ReactCompilerTarget>,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum ReactCompilerOptionsOrBoolean {
Boolean(bool),
Option(ReactCompilerOptions),
}
#[turbo_tasks::value(transparent)]
pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
/// Serialized representation of a path pattern for `turbopack.ignoreIssue`.
/// Strings are serialized as `{ "type": "glob", "value": "..." }` and
/// RegExp as `{ "type": "regex", "source": "...", "flags": "..." }`.
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(tag = "type")]
pub enum TurbopackIgnoreIssuePathPattern {
#[serde(rename = "glob")]
Glob { value: RcStr },
#[serde(rename = "regex")]
Regex { source: RcStr, flags: RcStr },
}
impl TurbopackIgnoreIssuePathPattern {
fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
match self {
TurbopackIgnoreIssuePathPattern::Glob { value } => Ok(IgnoreIssuePattern::Glob(
Glob::parse(value.clone(), GlobOptions::default())?,
)),
TurbopackIgnoreIssuePathPattern::Regex { source, flags } => {
Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
}
}
}
}
/// Serialized representation of a text pattern (title/description) for
/// `turbopack.ignoreIssue`. Strings are serialized as
/// `{ "type": "string", "value": "..." }` and RegExp as
/// `{ "type": "regex", "source": "...", "flags": "..." }`.
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(tag = "type")]
pub enum TurbopackIgnoreIssueTextPattern {
#[serde(rename = "string")]
String { value: RcStr },
#[serde(rename = "regex")]
Regex { source: RcStr, flags: RcStr },
}
impl TurbopackIgnoreIssueTextPattern {
fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
match self {
TurbopackIgnoreIssueTextPattern::String { value } => {
Ok(IgnoreIssuePattern::ExactString(value.clone()))
}
TurbopackIgnoreIssueTextPattern::Regex { source, flags } => {
Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
}
}
}
}
/// A single rule in `turbopack.ignoreIssue`.
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
pub struct TurbopackIgnoreIssueRule {
pub path: TurbopackIgnoreIssuePathPattern,
#[serde(default)]
pub title: Option<TurbopackIgnoreIssueTextPattern>,
#[serde(default)]
pub description: Option<TurbopackIgnoreIssueTextPattern>,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Deserialize,
TraceRawVcs,
ValueDebugFormat,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct ExperimentalConfig {
// all fields should be private and access should be wrapped within a turbo-tasks function
// Otherwise changing ExperimentalConfig will lead to invalidating all tasks accessing it.
allowed_revalidate_header_keys: Option<Vec<RcStr>>,
client_router_filter: Option<bool>,
/// decimal for percent for possible false positives e.g. 0.01 for 10%
/// potential false matches lower percent increases size of the filter
client_router_filter_allowed_rate: Option<f64>,
client_router_filter_redirects: Option<bool>,
fetch_cache_key_prefix: Option<RcStr>,
isr_flush_to_disk: Option<bool>,
/// For use with `@next/mdx`. Compile MDX files using the new Rust compiler.
/// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs)
mdx_rs: Option<MdxRsOptions>,
strict_next_head: Option<bool>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
swc_env_options: Option<SwcEnvOptions>,
external_middleware_rewrites_resolve: Option<bool>,
scroll_restoration: Option<bool>,
manual_client_base_path: Option<bool>,
optimistic_client_cache: Option<bool>,
middleware_prefetch: Option<MiddlewarePrefetchType>,
/// optimizeCss can be boolean or critters' option object
/// Use Record<string, unknown> as critters doesn't export its Option type ([link](https://github.com/GoogleChromeLabs/critters/blob/a590c05f9197b656d2aeaae9369df2483c26b072/packages/critters/src/index.d.ts))
#[bincode(with = "turbo_bincode::serde_self_describing")]
optimize_css: Option<serde_json::Value>,
next_script_workers: Option<bool>,
web_vitals_attribution: Option<Vec<RcStr>>,
server_actions: Option<ServerActionsOrLegacyBool>,
sri: Option<SubResourceIntegrity>,
/// @deprecated - use top-level cache_components instead.
/// This field is kept for backwards compatibility during migration.
cache_components: Option<bool>,
use_cache: Option<bool>,
root_params: Option<bool>,
runtime_server_deployment_id: Option<bool>,
supports_immutable_assets: Option<bool>,
/// A salt to mix into chunk and asset content hashes. Empty string means
/// no salt.
output_hash_salt: Option<RcStr>,
// ---
// UNSUPPORTED
// ---
adjust_font_fallbacks: Option<bool>,
adjust_font_fallbacks_with_size_adjust: Option<bool>,
after: Option<bool>,
app_document_preloading: Option<bool>,
app_new_scroll_handler: Option<bool>,
case_sensitive_routes: Option<bool>,
cpus: Option<f64>,
cra_compat: Option<bool>,
disable_optimized_loading: Option<bool>,
disable_postcss_preset_env: Option<bool>,
esm_externals: Option<EsmExternals>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
extension_alias: Option<serde_json::Value>,
external_dir: Option<bool>,
/// If set to `false`, webpack won't fall back to polyfill Node.js modules
/// in the browser Full list of old polyfills is accessible here:
/// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
fallback_node_polyfills: Option<bool>, // false
force_swc_transforms: Option<bool>,
fully_specified: Option<bool>,
gzip_size: Option<bool>,
inline_css: Option<bool>,
instrumentation_hook: Option<bool>,
client_trace_metadata: Option<Vec<String>>,
large_page_data_bytes: Option<f64>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
logging: Option<serde_json::Value>,
memory_based_workers_count: Option<bool>,
/// Optimize React APIs for server builds.
optimize_server_react: Option<bool>,
/// Automatically apply the "modularize_imports" optimization to imports of
/// the specified packages.
optimize_package_imports: Option<Vec<RcStr>>,
taint: Option<bool>,
proxy_timeout: Option<f64>,
/// enables the minification of server code.
server_minification: Option<bool>,
/// Enables source maps generation for the server production bundle.
server_source_maps: Option<bool>,
swc_trace_profiling: Option<bool>,
transition_indicator: Option<bool>,
gesture_transition: Option<bool>,
/// @internal Used by the Next.js internals only.
trust_host_header: Option<bool>,
#[bincode(with = "turbo_bincode::serde_self_describing")]
url_imports: Option<serde_json::Value>,
/// This option is to enable running the Webpack build in a worker thread
/// (doesn't apply to Turbopack).
webpack_build_worker: Option<bool>,
worker_threads: Option<bool>,
turbopack_minify: Option<bool>,
turbopack_module_ids: Option<ModuleIds>,
turbopack_plugin_runtime_strategy: Option<TurbopackPluginRuntimeStrategy>,
turbopack_source_maps: Option<bool>,
turbopack_input_source_maps: Option<bool>,
turbopack_tree_shaking: Option<bool>,
turbopack_scope_hoisting: Option<bool>,
/// Custom URL prefix for Web Worker URLs (the entrypoint and the module
/// chunks loaded inside the worker) produced by
/// `new Worker(new URL(..., import.meta.url))`. Mirrors webpack's
/// `output.workerPublicPath`. When unset, Worker URLs use the regular
/// chunk base path (i.e. `assetPrefix` + `/_next/`).
///
/// Like `assetPrefix`, the value is a prefix without a trailing slash
/// and without `/_next` — `/_next/` is appended automatically. An empty
/// string is a literal empty prefix; only `None` falls back to
/// `assetPrefix`.
turbopack_worker_asset_prefix: Option<RcStr>,
turbopack_client_side_nested_async_chunking: Option<bool>,
turbopack_server_side_nested_async_chunking: Option<bool>,
turbopack_import_type_bytes: Option<bool>,
turbopack_import_type_text: Option<bool>,
/// Disable automatic configuration of the sass loader.
#[serde(default)]
turbopack_use_builtin_sass: Option<bool>,
/// Disable automatic configuration of the babel loader when a babel configuration file is
/// present.
#[serde(default)]
turbopack_use_builtin_babel: Option<bool>,
/// Enable per-directory PostCSS config resolution. When true, Turbopack
/// searches for postcss.config.js starting from the CSS file's parent
/// directory first, then falls back to the project root.
#[serde(default)]
turbopack_local_postcss_config: Option<bool>,
// Whether to enable the global-not-found convention
global_not_found: Option<bool>,
/// Defaults to false in development mode, true in production mode.
turbopack_remove_unused_imports: Option<bool>,
/// Defaults to false in development mode, true in production mode.
turbopack_remove_unused_exports: Option<bool>,
/// Enable local analysis to infer side effect free modules. Defaults to true.
turbopack_infer_module_side_effects: Option<bool>,
/// Devtool option for the segment explorer.
devtool_segment_explorer: Option<bool>,
/// Whether to report inlined system environment variables as warnings or errors.
report_system_env_inlining: Option<String>,
// Use project.is_persistent_caching() instead
// turbopack_file_system_cache_for_dev: Option<bool>,
// turbopack_file_system_cache_for_build: Option<bool>,
lightning_css_features: Option<LightningCssFeatures>,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct SubResourceIntegrity {
pub algorithm: Option<RcStr>,
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct LightningCssFeatures {
pub include: Option<Vec<RcStr>>,
pub exclude: Option<Vec<RcStr>>,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum ServerActionsOrLegacyBool {
/// The current way to configure server actions sub behaviors.
ServerActionsConfig(ServerActions),
/// The legacy way to disable server actions. This is no longer used, server
/// actions is always enabled.
LegacyBool(bool),
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum EsmExternalsValue {
Loose,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum EsmExternals {
Loose(EsmExternalsValue),
Bool(bool),
}
// Test for esm externals deserialization.
#[test]
fn test_esm_externals_deserialization() {
let json = serde_json::json!({
"esmExternals": true
});
let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
let json = serde_json::json!({
"esmExternals": "loose"
});
let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
assert_eq!(
config.esm_externals,
Some(EsmExternals::Loose(EsmExternalsValue::Loose))
);
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct ServerActions {
/// Allows adjusting body parser size limit for server actions.
pub body_size_limit: Option<SizeLimit>,
}
#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
#[serde(untagged)]
pub enum SizeLimit {
Number(f64),
WithUnit(String),
}
// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
// implement Eq.
impl PartialEq for SizeLimit {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
(SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
_ => false,
}
}
}
impl Eq for SizeLimit {}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum MiddlewarePrefetchType {
Strict,
Flexible,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum EmotionTransformOptionsOrBoolean {
Boolean(bool),
Options(EmotionTransformConfig),
}
impl EmotionTransformOptionsOrBoolean {
pub fn is_enabled(&self) -> bool {
match self {
Self::Boolean(enabled) => *enabled,
_ => true,
}
}
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum StyledComponentsTransformOptionsOrBoolean {
Boolean(bool),
Options(StyledComponentsTransformConfig),
}
impl StyledComponentsTransformOptionsOrBoolean {
pub fn is_enabled(&self) -> bool {
match self {
Self::Boolean(enabled) => *enabled,
_ => true,
}
}
}
#[turbo_tasks::value(eq = "manual")]
#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompilerConfig {
pub react_remove_properties: Option<ReactRemoveProperties>,
pub relay: Option<RelayConfig>,
pub emotion: Option<EmotionTransformOptionsOrBoolean>,
pub remove_console: Option<RemoveConsoleConfig>,
pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged, rename_all = "camelCase")]
pub enum ReactRemoveProperties {
Boolean(bool),
Config { properties: Option<Vec<String>> },
}
impl ReactRemoveProperties {
pub fn is_enabled(&self) -> bool {
match self {
Self::Boolean(enabled) => *enabled,
_ => true,
}
}
}
#[derive(
Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
)]
#[serde(untagged)]
pub enum RemoveConsoleConfig {
Boolean(bool),
Config { exclude: Option<Vec<String>> },
}
impl RemoveConsoleConfig {
pub fn is_enabled(&self) -> bool {
match self {
Self::Boolean(enabled) => *enabled,
_ => true,
}
}
}
#[turbo_tasks::value(transparent)]
pub struct ResolveExtensions(Option<Vec<RcStr>>);
#[turbo_tasks::value(transparent)]
pub struct SwcPlugins(
#[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
);
/// Options for SWC's preset-env, exposed via `experimental.swcEnvOptions`.
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
Serialize,
Deserialize,
TraceRawVcs,
NonLocalValue,
OperationValue,
Encode,
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct SwcEnvOptions {
pub mode: Option<RcStr>,
pub core_js: Option<RcStr>,
pub skip: Option<Vec<RcStr>>,
pub include: Option<Vec<RcStr>>,
pub exclude: Option<Vec<RcStr>>,
pub shipped_proposals: Option<bool>,
pub force_all_transforms: Option<bool>,
pub debug: Option<bool>,
pub loose: Option<bool>,
}
#[turbo_tasks::value(transparent)]
pub struct OptionSwcEnvOptions(Option<SwcEnvOptions>);
#[turbo_tasks::value(transparent)]
pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
#[turbo_tasks::value(transparent)]
pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
#[turbo_tasks::value(transparent)]
pub struct OptionFileSystemPath(Option<FileSystemPath>);
#[turbo_tasks::value(transparent)]
pub struct IgnoreIssues(Vec<IgnoreIssue>);
#[turbo_tasks::value(transparent)]
pub struct OptionJsonValue(
#[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
);
fn turbopack_config_documentation_link() -> RcStr {
rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
}
#[turbo_tasks::value(shared)]
struct InvalidLoaderRuleRenameAsIssue {
glob: RcStr,
rename_as: RcStr,
config_file_path: FileSystemPath,
}
#[async_trait]
#[turbo_tasks::value_impl]
impl Issue for InvalidLoaderRuleRenameAsIssue {
async fn file_path(&self) -> Result<FileSystemPath> {
Ok(self.config_file_path.clone())
}
fn stage(&self) -> IssueStage {
IssueStage::Config
}
async fn title(&self) -> Result<StyledString> {
Ok(StyledString::Text(
format!("Invalid loader rule for extension: {}", self.glob).into(),
))
}
async fn description(&self) -> Result<Option<StyledString>> {
Ok(Some(StyledString::Text(RcStr::from(format!(
"The extension {} contains a wildcard, but the `as` option does not: {}",
self.glob, self.rename_as,
)))))
}
fn documentation_link(&self) -> RcStr {
turbopack_config_documentation_link()
}
}
#[turbo_tasks::value(shared)]
struct InvalidLoaderRuleConditionIssue {
error_string: RcStr,
condition: ConfigConditionItem,
config_file_path: FileSystemPath,
}
#[async_trait]
#[turbo_tasks::value_impl]
impl Issue for InvalidLoaderRuleConditionIssue {
async fn file_path(&self) -> Result<FileSystemPath> {
Ok(self.config_file_path.clone())
}
fn stage(&self) -> IssueStage {
IssueStage::Config
}
async fn title(&self) -> Result<StyledString> {
Ok(StyledString::Text(rcstr!(
"Invalid condition for Turbopack loader rule"
)))
}
async fn description(&self) -> Result<Option<StyledString>> {
Ok(Some(StyledString::Stack(vec![
StyledString::Line(vec![
StyledString::Text(rcstr!("Encountered the following error: ")),
StyledString::Code(self.error_string.clone()),
]),
StyledString::Text(rcstr!("While processing the condition:")),
StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
])))
}
fn documentation_link(&self) -> RcStr {
turbopack_config_documentation_link()
}
}
#[turbo_tasks::value_impl]
impl NextConfig {
#[turbo_tasks::function]
pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
let string = string.await?;
let mut jdeserializer = serde_json::Deserializer::from_str(&string);
let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
.with_context(|| format!("failed to parse next.config.js: {string}"))?;
Ok(config.cell())
}
#[turbo_tasks::function]
pub async fn config_file_path(
&self,
project_path: FileSystemPath,
) -> Result<Vc<FileSystemPath>> {
Ok(project_path.join(&self.config_file_name)?.cell())
}
#[turbo_tasks::function]
pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
}
#[turbo_tasks::function]
pub fn enable_react_production_profiling(&self) -> Vc<bool> {
Vc::cell(self.react_production_profiling.unwrap_or_default())
}
#[turbo_tasks::function]
pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
Vc::cell(
self.server_external_packages
.as_ref()
.cloned()
.unwrap_or_default(),
)
}
#[turbo_tasks::function]
pub fn is_standalone(&self) -> Vc<bool> {
Vc::cell(self.output == Some(OutputType::Standalone))
}
#[turbo_tasks::function]
pub fn base_path(&self) -> Vc<Option<RcStr>> {
Vc::cell(self.base_path.clone())
}
#[turbo_tasks::function]
pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
if let Some(handler) = &self.cache_handler {
Ok(Vc::cell(Some(project_path.join(handler)?)))
} else {
Ok(Vc::cell(None))
}
}
#[turbo_tasks::function]
pub fn compiler(&self) -> Vc<CompilerConfig> {
self.compiler.clone().unwrap_or_default().cell()
}
#[turbo_tasks::function]
pub fn env(&self) -> Vc<EnvMap> {
// The value expected for env is Record<String, String>, but config itself
// allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
// then stringifies it. We do the interop here as well.
let env = self
.env
.iter()
.map(|(k, v)| {
(
k.as_str().into(),
if let JsonValue::String(s) = v {
// A string value is kept, calling `to_string` would wrap in to quotes.
s.as_str().into()
} else {
v.to_string().into()
},
)
})
.collect();
Vc::cell(env)
}
#[turbo_tasks::function]
pub fn image_config(&self) -> Vc<ImageConfig> {
self.images.clone().cell()
}
#[turbo_tasks::function]
pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
// Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js,
// which just builds a regex alternative, which greedily matches the longest
// extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409
let mut extensions = self.page_extensions.clone();
extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
Vc::cell(extensions)
}
#[turbo_tasks::function]
pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
Vc::cell(self.experimental.global_not_found.unwrap_or_default())
}
#[turbo_tasks::function]
pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
Vc::cell(self.transpile_packages.clone().unwrap_or_default())
}
#[turbo_tasks::function]
pub async fn webpack_rules(
self: Vc<Self>,
project_path: FileSystemPath,
) -> Result<Vc<WebpackRules>> {
let this = self.await?;
let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
return Ok(Vc::cell(Vec::new()));
};
if turbo_rules.is_empty() {
return Ok(Vc::cell(Vec::new()));
}
let mut rules = Vec::new();
for (glob, rule_collection) in turbo_rules.iter() {
fn transform_loaders(
loaders: &mut dyn Iterator<Item = &LoaderItem>,
) -> ResolvedVc<WebpackLoaderItems> {
ResolvedVc::cell(
loaders
.map(|item| match item {
LoaderItem::LoaderName(name) => WebpackLoaderItem {
loader: name.clone(),
options: Default::default(),
},
LoaderItem::LoaderOptions(options) => options.clone(),
})
.collect(),
)
}
for item in &rule_collection.0 {
match item {
RuleConfigCollectionItem::Shorthand(loaders) => {
rules.push((
glob.clone(),
LoaderRuleItem {
loaders: transform_loaders(&mut [loaders].into_iter()),
rename_as: None,
condition: None,
module_type: None,
},
));
}
RuleConfigCollectionItem::Full(RuleConfigItem {
loaders,
rename_as,
condition,
module_type,
}) => {
// If the extension contains a wildcard, and the rename_as does not,
// emit an issue to prevent users from encountering duplicate module
// names.
if glob.contains("*")
&& let Some(rename_as) = rename_as.as_ref()
&& !rename_as.contains("*")
{
InvalidLoaderRuleRenameAsIssue {
glob: glob.clone(),
config_file_path: self
.config_file_path(project_path.clone())
.owned()
.await?,
rename_as: rename_as.clone(),
}
.resolved_cell()
.emit();
}
// convert from Next.js-specific condition type to internal Turbopack
// condition type
let condition = if let Some(condition) = condition {
match ConditionItem::try_from(condition.clone()) {
Ok(cond) => Some(cond),
Err(err) => {
InvalidLoaderRuleConditionIssue {
error_string: RcStr::from(err.to_string()),
condition: condition.clone(),
config_file_path: self
.config_file_path(project_path.clone())
.owned()
.await?,
}
.resolved_cell()
.emit();
None
}
}
} else {
None
};
rules.push((
glob.clone(),
LoaderRuleItem {
loaders: transform_loaders(&mut loaders.iter()),
rename_as: rename_as.clone(),
condition,
module_type: module_type.clone(),
},
));
}
}
}
}
Ok(Vc::cell(rules))
}
#[turbo_tasks::function]
pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
let Some(resolve_alias) = self
.turbopack
.as_ref()
.and_then(|t| t.resolve_alias.as_ref())
else {
return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
};
let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
Ok(alias_map.cell())
}
#[turbo_tasks::function]
pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
let Some(resolve_extensions) = self
.turbopack
.as_ref()
.and_then(|t| t.resolve_extensions.as_ref())
else {
return Vc::cell(None);
};
Vc::cell(Some(resolve_extensions.clone()))
}
#[turbo_tasks::function]
pub fn import_externals(&self) -> Result<Vc<bool>> {
Ok(Vc::cell(match self.experimental.esm_externals {
Some(EsmExternals::Bool(b)) => b,
Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
None => true,
}))
}
#[turbo_tasks::function]
pub fn inline_css(&self) -> Vc<bool> {
Vc::cell(self.experimental.inline_css.unwrap_or(false))
}
#[turbo_tasks::function]
pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
let options = &self.experimental.mdx_rs;
let options = match options {
Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
MdxTransformOptions {
provider_import_source: Some(mdx_import_source_file()),
..Default::default()
}
.resolved_cell(),
)),
Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
MdxTransformOptions {
provider_import_source: Some(
options
.provider_import_source
.clone()
.unwrap_or(mdx_import_source_file()),
),
..options.clone()
}
.resolved_cell(),
)),
_ => OptionalMdxTransformOptions(None),
};
options.cell()
}
#[turbo_tasks::function]
pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
Vc::cell(self.modularize_imports.clone().unwrap_or_default())
}
#[turbo_tasks::function]
pub fn dist_dir(&self) -> Vc<RcStr> {
Vc::cell(self.dist_dir.clone())
}
#[turbo_tasks::function]
pub fn dist_dir_root(&self) -> Vc<RcStr> {
Vc::cell(self.dist_dir_root.clone())
}
#[turbo_tasks::function]
pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
if let Some(handlers) = &self.cache_handlers {
Ok(Vc::cell(
handlers
.values()
.map(|h| project_path.join(h))
.collect::<Result<Vec<_>>>()?,
))
} else {
Ok(Vc::cell(vec![]))
}
}
#[turbo_tasks::function]
pub fn cache_handlers_map(&self) -> Vc<CacheHandlersMap> {
Vc::cell(self.cache_handlers.clone().unwrap_or_default())
}
#[turbo_tasks::function]
pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
}
#[turbo_tasks::function]
pub fn experimental_swc_env_options(&self) -> Vc<OptionSwcEnvOptions> {
Vc::cell(self.experimental.swc_env_options.clone())
}
#[turbo_tasks::function]
pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
Vc::cell(self.experimental.sri.clone())
}
#[turbo_tasks::function]
pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
Vc::cell(self.experimental.turbopack_use_builtin_babel)
}
#[turbo_tasks::function]
pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
Vc::cell(self.experimental.turbopack_use_builtin_sass)
}
#[turbo_tasks::function]
pub fn experimental_turbopack_local_postcss_config(&self) -> Vc<Option<bool>> {
Vc::cell(self.experimental.turbopack_local_postcss_config)
}
#[turbo_tasks::function]
pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
let options = &self.react_compiler;
let options = match options {
Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
}
Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
),
_ => OptionalReactCompilerOptions(None),
};
options.cell()
}
#[turbo_tasks::function]
pub fn sass_config(&self) -> Vc<JsonValue> {
Vc::cell(self.sass_options.clone().unwrap_or_default())
}
#[turbo_tasks::function]
pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
}
#[turbo_tasks::function]
pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
}
/// Returns the final asset prefix. If an assetPrefix is set, it's used.
/// Otherwise, the basePath is used.
#[turbo_tasks::function]
pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
let this = self.await?;
Ok(Vc::cell(
format!(
"{}/_next/",
if let Some(asset_prefix) = &this.asset_prefix {
asset_prefix
} else {
this.base_path.as_ref().map_or("", |b| b.as_str())
}
.trim_end_matches('/')
)
.into(),
))
}
/// Returns the suffix to use for chunk loading.
#[turbo_tasks::function]
pub fn asset_suffix_path(&self) -> Vc<Option<RcStr>> {
let needs_dpl_id = self
.experimental
.supports_immutable_assets
.is_none_or(|f| !f);
Vc::cell(
needs_dpl_id
.then_some(self.deployment_id.as_ref())
.flatten()
.map(|id| format!("?dpl={id}").into()),
)
}
/// Whether to enable immutable assets, which uses a different asset suffix, and writes a
/// .next/immutable-static-hashes.json manifest.
#[turbo_tasks::function]
pub fn enable_immutable_assets(&self) -> Vc<bool> {
Vc::cell(self.experimental.supports_immutable_assets == Some(true))
}
#[turbo_tasks::function]
pub fn client_static_folder_name(&self) -> Vc<RcStr> {
Vc::cell(
if self.experimental.supports_immutable_assets == Some(true) {
// Ends up as `_next/static/immutable`
rcstr!("static/immutable")
} else {
rcstr!("static")
},
)
}
#[turbo_tasks::function]
pub fn enable_taint(&self) -> Vc<bool> {
Vc::cell(self.experimental.taint.unwrap_or(false))
}
#[turbo_tasks::function]
pub fn enable_transition_indicator(&self) -> Vc<bool> {
Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
}
#[turbo_tasks::function]
pub fn enable_gesture_transition(&self) -> Vc<bool> {
Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
}
#[turbo_tasks::function]
pub fn enable_cache_components(&self) -> Vc<bool> {
Vc::cell(self.cache_components.unwrap_or(false))
}
#[turbo_tasks::function]
pub fn enable_use_cache(&self) -> Vc<bool> {
Vc::cell(
self.experimental
.use_cache
// "use cache" was originally implicitly enabled with the
// cacheComponents flag, so we transfer the value for cacheComponents to the
// explicit useCache flag to ensure backwards compatibility.
.unwrap_or(self.cache_components.unwrap_or(false)),
)
}
#[turbo_tasks::function]
pub fn enable_root_params(&self) -> Vc<bool> {
Vc::cell(
self.experimental
.root_params
// rootParams should be enabled implicitly in cacheComponents.
.unwrap_or(self.cache_components.unwrap_or(false)),
)
}
#[turbo_tasks::function]
pub fn should_append_server_deployment_id_at_runtime(&self) -> Vc<bool> {
let needs_dpl_id = self
.experimental
.supports_immutable_assets
.is_none_or(|f| !f);
Vc::cell(
needs_dpl_id
&& self
.experimental
.runtime_server_deployment_id
.unwrap_or(false),
)
}
#[turbo_tasks::function]
pub fn cache_kinds(&self) -> Vc<CacheKinds> {
let mut cache_kinds = CacheKinds::default();
if let Some(handlers) = self.cache_handlers.as_ref() {
cache_kinds.extend(handlers.keys().cloned());
}
cache_kinds.cell()
}
#[turbo_tasks::function]
pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
Vc::cell(
self.experimental
.optimize_package_imports
.clone()
.unwrap_or_default(),
)
}
#[turbo_tasks::function]
pub fn tree_shaking_mode_for_foreign_code(
&self,
_is_development: bool,
) -> Vc<OptionTreeShaking> {
OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
Some(false) => Some(TreeShakingMode::ReexportsOnly),
Some(true) => Some(TreeShakingMode::ModuleFragments),
None => Some(TreeShakingMode::ReexportsOnly),
})
.cell()
}
#[turbo_tasks::function]
pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
Some(false) => Some(TreeShakingMode::ReexportsOnly),
Some(true) => Some(TreeShakingMode::ModuleFragments),
None => Some(TreeShakingMode::ReexportsOnly),
})
.cell()
}
#[turbo_tasks::function]
pub async fn turbopack_remove_unused_imports(
self: Vc<Self>,
mode: Vc<NextMode>,
) -> Result<Vc<bool>> {
let remove_unused_imports = self
.await?
.experimental
.turbopack_remove_unused_imports
.unwrap_or(matches!(*mode.await?, NextMode::Build));
if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
bail!(
"`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
enabling `experimental.turbopackRemoveUnusedExports`"
);
}
Ok(Vc::cell(remove_unused_imports))
}
#[turbo_tasks::function]
pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
Ok(Vc::cell(
self.experimental
.turbopack_remove_unused_exports
.unwrap_or(matches!(*mode.await?, NextMode::Build)),
))
}
#[turbo_tasks::function]
pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
Vc::cell(
self.experimental
.turbopack_infer_module_side_effects
.unwrap_or(true),
)
}
#[turbo_tasks::function]
pub fn turbopack_plugin_runtime_strategy(&self) -> Vc<TurbopackPluginRuntimeStrategy> {
#[cfg(feature = "process_pool")]
let default = TurbopackPluginRuntimeStrategy::ChildProcesses;
#[cfg(all(feature = "worker_pool", not(feature = "process_pool")))]
let default = TurbopackPluginRuntimeStrategy::WorkerThreads;
self.experimental
.turbopack_plugin_runtime_strategy
.unwrap_or(default)
.cell()
}
#[turbo_tasks::function]
pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
Ok(match *mode.await? {
// Ignore configuration in development mode, HMR only works with `named`
NextMode::Development => ModuleIds::Named.cell(),
NextMode::Build => self
.experimental
.turbopack_module_ids
.unwrap_or(ModuleIds::Deterministic)
.cell(),
})
}
#[turbo_tasks::function]
pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
let minify = self.experimental.turbopack_minify;
Ok(Vc::cell(
minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
))
}
#[turbo_tasks::function]
pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
Ok(Vc::cell(match *mode.await? {
// Ignore configuration in development mode to not break HMR
NextMode::Development => false,
NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
}))
}
#[turbo_tasks::function]
pub async fn turbo_nested_async_chunking(
&self,
mode: Vc<NextMode>,
client_side: bool,
) -> Result<Vc<bool>> {
let option = if client_side {
self.experimental
.turbopack_client_side_nested_async_chunking
} else {
self.experimental
.turbopack_server_side_nested_async_chunking
};
Ok(Vc::cell(if let Some(value) = option {
value
} else {
match *mode.await? {
NextMode::Development => false,
NextMode::Build => client_side,
}
}))
}
#[turbo_tasks::function]
pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
Vc::cell(
self.experimental
.turbopack_import_type_bytes
.unwrap_or(false),
)
}
#[turbo_tasks::function]
pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
Vc::cell(
self.experimental
.turbopack_import_type_text
.unwrap_or(false),
)
}
#[turbo_tasks::function]
pub fn lightningcss_feature_flags(
&self,
) -> Result<Vc<turbopack_css::LightningCssFeatureFlags>> {
Ok(turbopack_css::LightningCssFeatureFlags {
include: lightningcss_features_field_mask(
&self.experimental.lightning_css_features,
|f| f.include.as_ref(),
)?,
exclude: lightningcss_features_field_mask(
&self.experimental.lightning_css_features,
|f| f.exclude.as_ref(),
)?,
}
.cell())
}
#[turbo_tasks::function]
pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
let input_source_maps = self
.experimental
.turbopack_input_source_maps
.unwrap_or(true);
let source_maps = self
.experimental
.turbopack_source_maps
.unwrap_or(match &*mode.await? {
NextMode::Development => true,
NextMode::Build => self.production_browser_source_maps,
});
Ok(match (source_maps, input_source_maps) {
(true, true) => SourceMapsType::Full,
(true, false) => SourceMapsType::Partial,
(false, _) => SourceMapsType::None,
}
.cell())
}
#[turbo_tasks::function]
pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
let input_source_maps = self
.experimental
.turbopack_input_source_maps
.unwrap_or(true);
let source_maps = self
.experimental
.turbopack_source_maps
.or(self.experimental.server_source_maps)
.unwrap_or(true);
Ok(match (source_maps, input_source_maps) {
(true, true) => SourceMapsType::Full,
(true, false) => SourceMapsType::Partial,
(false, _) => SourceMapsType::None,
}
.cell())
}
#[turbo_tasks::function]
pub fn turbopack_debug_ids(&self) -> Vc<bool> {
Vc::cell(
self.turbopack
.as_ref()
.and_then(|turbopack| turbopack.debug_ids)
.unwrap_or(false),
)
}
/// Returns the resolved worker chunk base path with `/_next/` appended,
/// or `None` to fall back to the regular chunk base path.
#[turbo_tasks::function]
pub fn turbopack_worker_asset_prefix(&self) -> Vc<Option<RcStr>> {
Vc::cell(
self.experimental
.turbopack_worker_asset_prefix
.as_ref()
.map(|prefix| format!("{}/_next/", prefix.trim_end_matches('/')).into()),
)
}
#[turbo_tasks::function]
pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
Ok(Vc::cell(
self.typescript
.tsconfig_path
.as_ref()
.map(|path| path.to_owned().into()),
))
}
#[turbo_tasks::function]
pub fn cross_origin(&self) -> Vc<CrossOrigin> {
*self.cross_origin.resolved_cell()
}
#[turbo_tasks::function]
pub fn i18n(&self) -> Vc<OptionI18NConfig> {
Vc::cell(self.i18n.clone())
}
#[turbo_tasks::function]
pub fn output(&self) -> Vc<OptionOutputType> {
Vc::cell(self.output.clone())
}
#[turbo_tasks::function]
pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
Vc::cell(self.output_file_tracing_includes.clone())
}
#[turbo_tasks::function]
pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
Vc::cell(self.output_file_tracing_excludes.clone())
}
#[turbo_tasks::function]
pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
FetchClientConfig::default().cell()
}
#[turbo_tasks::function]
pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
match self.experimental.report_system_env_inlining.as_deref() {
None => Ok(IssueSeverity::Suggestion.cell()),
Some("warn") => Ok(IssueSeverity::Warning.cell()),
Some("error") => Ok(IssueSeverity::Error.cell()),
_ => bail!(
"`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
),
}
}
/// Returns the list of ignore-issue rules from the turbopack config,
/// converted to the `IgnoreIssue` type used by `IssueFilter`.
#[turbo_tasks::function]
pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
let rules = self
.turbopack
.as_ref()
.and_then(|tp| tp.ignore_issue.as_deref())
.unwrap_or_default()
.iter()
.map(|rule| {
Ok(IgnoreIssue {
path: rule.path.to_ignore_pattern()?,
title: rule
.title
.as_ref()
.map(|t| t.to_ignore_pattern())
.transpose()?,
description: rule
.description
.as_ref()
.map(|d| d.to_ignore_pattern())
.transpose()?,
})
})
.collect::<Result<Vec<_>>>()?;
Ok(Vc::cell(rules))
}
#[turbo_tasks::function]
pub fn output_hash_salt(&self) -> Vc<RcStr> {
Vc::cell(
self.experimental
.output_hash_salt
.clone()
.unwrap_or_default(),
)
}
}
/// A subset of ts/jsconfig that next.js implicitly
/// interops with.
#[turbo_tasks::value(serialization = "custom", eq = "manual")]
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
#[serde(rename_all = "camelCase")]
pub struct JsConfig {
#[bincode(with = "turbo_bincode::serde_self_describing")]
compiler_options: Option<serde_json::Value>,
}
#[turbo_tasks::value_impl]
impl JsConfig {
#[turbo_tasks::function]
pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
let string = string.await?;
let config: JsConfig = serde_json::from_str(&string)
.with_context(|| format!("failed to parse next.config.js: {string}"))?;
Ok(config.cell())
}
#[turbo_tasks::function]
pub fn compiler_options(&self) -> Vc<serde_json::Value> {
Vc::cell(self.compiler_options.clone().unwrap_or_default())
}
}
/// Extract either the `include` or `exclude` field from `LightningCssFeatures`
/// and convert the feature names to a bitmask.
fn lightningcss_features_field_mask(
features: &Option<LightningCssFeatures>,
field: impl FnOnce(&LightningCssFeatures) -> Option<&Vec<RcStr>>,
) -> Result<u32> {
features
.as_ref()
.and_then(field)
.map(|names| lightningcss_feature_names_to_mask(names))
.unwrap_or(Ok(0))
}
/// Convert dash-case feature name strings to a lightningcss `Features` bitmask.
///
/// Uses the canonical `Features` constants from the lightningcss crate.
/// Composite names (`selectors`, `media-queries`, `colors`) OR together the
/// bits of their constituent individual features.
///
/// Feature names must match: `packages/next/src/server/config-shared.ts`
/// (`LIGHTNINGCSS_FEATURE_NAMES`)
pub fn lightningcss_feature_names_to_mask(
names: &[impl std::ops::Deref<Target = str>],
) -> Result<u32> {
use lightningcss::targets::Features;
let mut mask = Features::empty();
for name in names {
mask |= match &**name {
"nesting" => Features::Nesting,
"not-selector-list" => Features::NotSelectorList,
"dir-selector" => Features::DirSelector,
"lang-selector-list" => Features::LangSelectorList,
"is-selector" => Features::IsSelector,
"text-decoration-thickness-percent" => Features::TextDecorationThicknessPercent,
"media-interval-syntax" => Features::MediaIntervalSyntax,
"media-range-syntax" => Features::MediaRangeSyntax,
"custom-media-queries" => Features::CustomMediaQueries,
"clamp-function" => Features::ClampFunction,
"color-function" => Features::ColorFunction,
"oklab-colors" => Features::OklabColors,
"lab-colors" => Features::LabColors,
"p3-colors" => Features::P3Colors,
"hex-alpha-colors" => Features::HexAlphaColors,
"space-separated-color-notation" => Features::SpaceSeparatedColorNotation,
"font-family-system-ui" => Features::FontFamilySystemUi,
"double-position-gradients" => Features::DoublePositionGradients,
"vendor-prefixes" => Features::VendorPrefixes,
"logical-properties" => Features::LogicalProperties,
"light-dark" => Features::LightDark,
// Composite groups
"selectors" => Features::Selectors,
"media-queries" => Features::MediaQueries,
"colors" => Features::Colors,
_ => bail!("Unknown lightningcss feature: {}", &**name),
};
}
Ok(mask.bits())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serde_rule_config_item_options() {
let json_value = serde_json::json!({
"loaders": [],
"as": "*.js",
"condition": {
"all": [
"production",
{"not": "foreign"},
{"any": [
"browser",
{
"path": { "type": "glob", "value": "*.svg"},
"query": {
"type": "regex",
"value": {
"source": "@someQuery",
"flags": ""
}
},
"content": {
"source": "@someTag",
"flags": ""
}
}
]},
],
}
});
let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
assert_eq!(
rule_config,
RuleConfigItem {
loaders: vec![],
rename_as: Some(rcstr!("*.js")),
module_type: None,
condition: Some(ConfigConditionItem::All(
[
ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
WebpackLoaderBuiltinCondition::Foreign
))),
ConfigConditionItem::Any(
vec![
ConfigConditionItem::Builtin(
WebpackLoaderBuiltinCondition::Browser
),
ConfigConditionItem::Base {
path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
content: Some(RegexComponents {
source: rcstr!("@someTag"),
flags: rcstr!(""),
}),
query: Some(ConfigConditionQuery::Regex(RegexComponents {
source: rcstr!("@someQuery"),
flags: rcstr!(""),
})),
content_type: None,
},
]
.into(),
),
]
.into(),
)),
}
);
}
}