From 5c421118e47ed2f7685be9f00b3ffae69bbe8ca4 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Fri, 4 Apr 2025 16:03:36 -0700 Subject: [PATCH 1/2] Turbopack: support `config.turbopack` and deprecate `config.experimental.turbopack`. This: - Adds support for using `config.turbopack` for Turbopack. The accepted value for this option is identical to the previous experimental one without the deprecated field `loaders`. - Deprecates `config.experimental.turbo`. Warns on its use, but will continue to accept it, merging it with `config.turbopack` while preferring fields on `config.turbopack`. Test Plan: - [ ] Convert use of `config.experimental.turbo` in tests to `config.turbopack` - [ ] Automated test for accepting the experimental option for compatibility, as well as the config merging --- bench/heavy-npm-deps/next.config.mjs | 4 +- crates/next-api/src/project.rs | 45 ++++---- crates/next-core/src/next_client/context.rs | 17 ++- crates/next-core/src/next_config.rs | 79 +++++-------- crates/next-core/src/next_server/context.rs | 17 ++- .../05-config/01-next-config-js/turbo.mdx | 10 +- docs/01-app/05-api-reference/08-turbopack.mdx | 2 +- examples/with-react-native-web/next.config.js | 34 +++--- .../with-turbopack-loaders/next.config.js | 20 ++-- packages/next/src/build/index.ts | 9 +- packages/next/src/build/swc/index.ts | 35 +++--- .../next/src/build/turbopack-build/impl.ts | 5 +- packages/next/src/lib/turbopack-warning.ts | 2 +- packages/next/src/server/config-schema.ts | 100 ++++++++++------- packages/next/src/server/config-shared.ts | 106 ++++++++++++------ packages/next/src/server/config.test.ts | 6 +- packages/next/src/server/config.ts | 55 +++++---- .../src/server/dev/hot-reloader-turbopack.ts | 4 +- .../next/src/shared/lib/turbopack/utils.ts | 2 +- .../react-owner-stacks-svgr/next.config.js | 12 +- .../app-dir/resolve-extensions/next.config.js | 6 +- .../npm-import-tilde/npm-import-tilde.test.ts | 10 +- .../next.config.js | 12 +- .../webpack-loader-conditions/next.config.js | 44 ++++---- .../next.config.js | 8 +- test/e2e/esm-externals/next.config.js | 8 +- test/e2e/persistent-caching/next.config.js | 20 ++-- .../app/layout.js | 7 ++ .../app/page.js | 5 + .../index.test.ts | 39 +++++++ .../next.config.js | 14 +++ .../turbo.js | 1 + .../turbopack.js | 1 + .../module-id-strategies/next.config.js | 10 -- .../components/CustomComponent.tsx | 0 .../module-with-long-name.js | 0 test/integration/module-ids/next.config.js | 4 + .../external-module-with-long-name.js | 0 .../pages/index.js | 0 .../test/index.test.js | 0 .../test/index.test.js | 1 + .../telemetry/next.config.persistent-cache | 6 +- 42 files changed, 424 insertions(+), 336 deletions(-) create mode 100644 test/e2e/turbopack-turbo-config-compatibility/app/layout.js create mode 100644 test/e2e/turbopack-turbo-config-compatibility/app/page.js create mode 100644 test/e2e/turbopack-turbo-config-compatibility/index.test.ts create mode 100644 test/e2e/turbopack-turbo-config-compatibility/next.config.js create mode 100644 test/e2e/turbopack-turbo-config-compatibility/turbo.js create mode 100644 test/e2e/turbopack-turbo-config-compatibility/turbopack.js delete mode 100644 test/integration/module-id-strategies/next.config.js rename test/integration/{module-id-strategies => module-ids}/components/CustomComponent.tsx (100%) rename test/integration/{module-id-strategies => module-ids}/module-with-long-name.js (100%) create mode 100644 test/integration/module-ids/next.config.js rename test/integration/{module-id-strategies => module-ids}/node_modules/external-module-with-long-name.js (100%) rename test/integration/{module-id-strategies => module-ids}/pages/index.js (100%) rename test/integration/{module-id-strategies => module-ids}/test/index.test.js (100%) diff --git a/bench/heavy-npm-deps/next.config.mjs b/bench/heavy-npm-deps/next.config.mjs index d53d676a170eb..debb2fb54b1ce 100644 --- a/bench/heavy-npm-deps/next.config.mjs +++ b/bench/heavy-npm-deps/next.config.mjs @@ -7,9 +7,7 @@ const nextConfig = { ignoreBuildErrors: true, }, experimental: { - turbo: { - unstablePersistentCaching: process.env.TURBO_CACHE === '1', - }, + turbopackPersistentCaching: process.env.TURBO_CACHE === '1', }, } diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index 88ef18cbf6d89..6afb9e449684f 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -11,7 +11,7 @@ use next_core::{ middleware::middleware_files, mode::NextMode, next_client::{get_client_chunking_context, get_client_compile_time_info}, - next_config::{JsConfig, ModuleIdStrategy as ModuleIdStrategyConfig, NextConfig}, + next_config::{JsConfig, ModuleIds as ModuleIdStrategyConfig, NextConfig}, next_server::{ get_server_chunking_context, get_server_chunking_context_with_client_assets, get_server_compile_time_info, get_server_module_options_context, @@ -773,7 +773,7 @@ impl Project { node_build_environment().to_resolved().await?, next_mode.runtime_type(), ) - .source_maps(if *self.next_config().turbo_source_maps().await? { + .source_maps(if *self.next_config().server_source_maps().await? { SourceMapsType::Full } else { SourceMapsType::None @@ -987,9 +987,9 @@ impl Project { self.next_config().chunk_suffix_path(), self.client_compile_time_info().environment(), self.next_mode(), - self.module_id_strategy(), + self.module_ids(), self.next_config().turbo_minify(self.next_mode()), - self.next_config().turbo_source_maps(), + self.next_config().client_source_maps(self.next_mode()), self.no_mangling(), ) } @@ -1008,9 +1008,9 @@ impl Project { self.client_relative_path(), self.next_config().computed_asset_prefix(), self.server_compile_time_info().environment(), - self.module_id_strategy(), + self.module_ids(), self.next_config().turbo_minify(self.next_mode()), - self.next_config().turbo_source_maps(), + self.next_config().server_source_maps(), self.no_mangling(), ) } else { @@ -1020,9 +1020,9 @@ impl Project { self.node_root(), self.node_root_to_root_path(), self.server_compile_time_info().environment(), - self.module_id_strategy(), + self.module_ids(), self.next_config().turbo_minify(self.next_mode()), - self.next_config().turbo_source_maps(), + self.next_config().server_source_maps(), self.no_mangling(), ) } @@ -1042,9 +1042,9 @@ impl Project { self.client_relative_path(), self.next_config().computed_asset_prefix(), self.edge_compile_time_info().environment(), - self.module_id_strategy(), + self.module_ids(), self.next_config().turbo_minify(self.next_mode()), - self.next_config().turbo_source_maps(), + self.next_config().server_source_maps(), self.no_mangling(), ) } else { @@ -1054,9 +1054,9 @@ impl Project { self.node_root(), self.node_root_to_root_path(), self.edge_compile_time_info().environment(), - self.module_id_strategy(), + self.module_ids(), self.next_config().turbo_minify(self.next_mode()), - self.next_config().turbo_source_maps(), + self.next_config().server_source_maps(), self.no_mangling(), ) } @@ -1709,17 +1709,16 @@ impl Project { /// Gets the module id strategy for the project. #[turbo_tasks::function] - pub async fn module_id_strategy(self: Vc) -> Result>> { - let module_id_strategy = if let Some(module_id_strategy) = - &*self.next_config().module_id_strategy_config().await? - { - *module_id_strategy - } else { - match *self.next_mode().await? { - NextMode::Development => ModuleIdStrategyConfig::Named, - NextMode::Build => ModuleIdStrategyConfig::Deterministic, - } - }; + pub async fn module_ids(self: Vc) -> Result>> { + let module_id_strategy = + if let Some(module_id_strategy) = &*self.next_config().module_ids().await? { + *module_id_strategy + } else { + match *self.next_mode().await? { + NextMode::Development => ModuleIdStrategyConfig::Named, + NextMode::Build => ModuleIdStrategyConfig::Deterministic, + } + }; match module_id_strategy { ModuleIdStrategyConfig::Named => Ok(Vc::upcast(DevModuleIdStrategy::new())), diff --git a/crates/next-core/src/next_client/context.rs b/crates/next-core/src/next_client/context.rs index 36d9caef0b627..9e152fefc0497 100644 --- a/crates/next-core/src/next_client/context.rs +++ b/crates/next-core/src/next_client/context.rs @@ -329,22 +329,19 @@ pub async fn get_client_module_options_context( let enable_postcss_transform = Some(postcss_transform_options.resolved_cell()); let enable_foreign_postcss_transform = Some(postcss_foreign_transform_options.resolved_cell()); + let source_maps = if *next_config.client_source_maps(mode).await? { + SourceMapsType::Full + } else { + SourceMapsType::None + }; let module_options_context = ModuleOptionsContext { ecmascript: EcmascriptOptionsContext { enable_typeof_window_inlining: Some(TypeofWindow::Object), - source_maps: if *next_config.turbo_source_maps().await? { - SourceMapsType::Full - } else { - SourceMapsType::None - }, + source_maps, ..Default::default() }, css: CssOptionsContext { - source_maps: if *next_config.turbo_source_maps().await? { - SourceMapsType::Full - } else { - SourceMapsType::None - }, + source_maps, ..Default::default() }, preset_env_versions: Some(env), diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index 07e4977451a9a..1636b6b3e4468 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -94,6 +94,8 @@ pub struct NextConfig { pub cross_origin: Option, pub dev_indicators: Option, pub output: Option, + pub turbopack: Option, + production_browser_source_maps: bool, /// Enables the bundling of node_modules packages (externals) for pages /// server-side bundles. @@ -129,7 +131,6 @@ pub struct NextConfig { http_agent_options: HttpAgentConfig, on_demand_entries: OnDemandEntriesConfig, powered_by_header: bool, - production_browser_source_maps: bool, public_runtime_config: FxIndexMap, server_runtime_config: FxIndexMap, static_page_generation_timeout: f64, @@ -536,17 +537,13 @@ pub enum RemotePatternProtocal { OperationValue, )] #[serde(rename_all = "camelCase")] -pub struct ExperimentalTurboConfig { +pub struct TurbopackConfig { /// This option has been replaced by `rules`. pub loaders: Option, pub rules: Option>, pub resolve_alias: Option>, pub resolve_extensions: Option>, - pub tree_shaking: Option, - pub module_id_strategy: Option, - pub minify: Option, - pub source_maps: Option, - pub unstable_persistent_caching: Option, + pub module_ids: Option, } #[derive( @@ -590,13 +587,13 @@ pub enum LoaderItem { #[turbo_tasks::value(operation)] #[derive(Copy, Clone, Debug)] #[serde(rename_all = "camelCase")] -pub enum ModuleIdStrategy { +pub enum ModuleIds { Named, Deterministic, } #[turbo_tasks::value(transparent)] -pub struct OptionModuleIdStrategy(pub Option); +pub struct OptionModuleIds(pub Option); #[derive( Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, @@ -668,7 +665,6 @@ pub struct ExperimentalConfig { mdx_rs: Option, strict_next_head: Option, swc_plugins: Option>, - turbo: Option, external_middleware_rewrites_resolve: Option, scroll_restoration: Option, manual_client_base_path: Option, @@ -748,6 +744,11 @@ pub struct ExperimentalConfig { /// (doesn't apply to Turbopack). webpack_build_worker: Option, worker_threads: Option, + + turbopack_minify: Option, + turbopack_persistent_caching: Option, + turbopack_source_maps: Option, + turbopack_tree_shaking: Option, } #[derive( @@ -1144,12 +1145,7 @@ impl NextConfig { #[turbo_tasks::function] pub fn webpack_rules(&self, active_conditions: Vec) -> Vc { - let Some(turbo_rules) = self - .experimental - .turbo - .as_ref() - .and_then(|t| t.rules.as_ref()) - else { + let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else { return Vc::cell(None); }; if turbo_rules.is_empty() { @@ -1234,9 +1230,7 @@ impl NextConfig { pub fn persistent_caching_enabled(&self) -> Result> { Ok(Vc::cell( self.experimental - .turbo - .as_ref() - .and_then(|t| t.unstable_persistent_caching) + .turbopack_persistent_caching .unwrap_or_default(), )) } @@ -1244,8 +1238,7 @@ impl NextConfig { #[turbo_tasks::function] pub fn resolve_alias_options(&self) -> Result> { let Some(resolve_alias) = self - .experimental - .turbo + .turbopack .as_ref() .and_then(|t| t.resolve_alias.as_ref()) else { @@ -1258,8 +1251,7 @@ impl NextConfig { #[turbo_tasks::function] pub fn resolve_extension(&self) -> Vc { let Some(resolve_extensions) = self - .experimental - .turbo + .turbopack .as_ref() .and_then(|t| t.resolve_extensions.as_ref()) else { @@ -1476,13 +1468,7 @@ impl NextConfig { &self, _is_development: bool, ) -> Vc { - let tree_shaking = self - .experimental - .turbo - .as_ref() - .and_then(|v| v.tree_shaking); - - OptionTreeShaking(match tree_shaking { + OptionTreeShaking(match self.experimental.turbopack_tree_shaking { Some(false) => Some(TreeShakingMode::ReexportsOnly), Some(true) => Some(TreeShakingMode::ModuleFragments), None => Some(TreeShakingMode::ReexportsOnly), @@ -1492,13 +1478,7 @@ impl NextConfig { #[turbo_tasks::function] pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc { - let tree_shaking = self - .experimental - .turbo - .as_ref() - .and_then(|v| v.tree_shaking); - - OptionTreeShaking(match tree_shaking { + OptionTreeShaking(match self.experimental.turbopack_tree_shaking { Some(false) => Some(TreeShakingMode::ReexportsOnly), Some(true) => Some(TreeShakingMode::ModuleFragments), None => Some(TreeShakingMode::ReexportsOnly), @@ -1507,31 +1487,32 @@ impl NextConfig { } #[turbo_tasks::function] - pub fn module_id_strategy_config(&self) -> Vc { - let Some(module_id_strategy) = self - .experimental - .turbo - .as_ref() - .and_then(|t| t.module_id_strategy) - else { + pub fn module_ids(&self) -> Vc { + let Some(module_ids) = self.turbopack.as_ref().and_then(|t| t.module_ids) else { return Vc::cell(None); }; - Vc::cell(Some(module_id_strategy)) + Vc::cell(Some(module_ids)) } #[turbo_tasks::function] pub async fn turbo_minify(&self, mode: Vc) -> Result> { - let minify = self.experimental.turbo.as_ref().and_then(|t| t.minify); - + let minify = self.experimental.turbopack_minify; Ok(Vc::cell( minify.unwrap_or(matches!(*mode.await?, NextMode::Build)), )) } #[turbo_tasks::function] - pub async fn turbo_source_maps(&self) -> Result> { - let source_maps = self.experimental.turbo.as_ref().and_then(|t| t.source_maps); + pub async fn client_source_maps(&self, _mode: Vc) -> Result> { + // Temporarily always enable client source maps as tests regress. + // TODO: Respect both `self.experimental.turbopack_source_maps` and + // `self.production_browser_source_maps` + Ok(Vc::cell(true)) + } + #[turbo_tasks::function] + pub async fn server_source_maps(&self) -> Result> { + let source_maps = self.experimental.turbopack_source_maps; Ok(Vc::cell(source_maps.unwrap_or(true))) } diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index 313fdaca39a98..4d5758b2637a2 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -536,25 +536,22 @@ pub async fn get_server_module_options_context( get_styled_components_transform_rule(next_config).await?; let styled_jsx_transform_rule = get_styled_jsx_transform_rule(next_config, versions).await?; + let source_maps = if *next_config.server_source_maps().await? { + SourceMapsType::Full + } else { + SourceMapsType::None + }; let module_options_context = ModuleOptionsContext { ecmascript: EcmascriptOptionsContext { enable_typeof_window_inlining: Some(TypeofWindow::Undefined), import_externals: *next_config.import_externals().await?, ignore_dynamic_requests: true, - source_maps: if *next_config.turbo_source_maps().await? { - SourceMapsType::Full - } else { - SourceMapsType::None - }, + source_maps, ..Default::default() }, execution_context: Some(execution_context), css: CssOptionsContext { - source_maps: if *next_config.turbo_source_maps().await? { - SourceMapsType::Full - } else { - SourceMapsType::None - }, + source_maps, ..Default::default() }, tree_shaking_mode: tree_shaking_mode_for_user_code, diff --git a/docs/01-app/05-api-reference/05-config/01-next-config-js/turbo.mdx b/docs/01-app/05-api-reference/05-config/01-next-config-js/turbo.mdx index 75c90902b6099..a5bc7801825ff 100644 --- a/docs/01-app/05-api-reference/05-config/01-next-config-js/turbo.mdx +++ b/docs/01-app/05-api-reference/05-config/01-next-config-js/turbo.mdx @@ -50,7 +50,7 @@ The following options are available for the `turbo` configuration: | `rules` | List of supported webpack loaders to apply when running with Turbopack. | | `resolveAlias` | Map aliased imports to modules to load in their place. | | `resolveExtensions` | List of extensions to resolve when importing files. | -| `moduleIdStrategy` | Assign module IDs | +| `moduleIds` | Assign module IDs | | `treeShaking` | Enable tree shaking for the turbopack dev server and build. | | `memoryLimit` | A target memory limit for turbo, in bytes. | @@ -157,14 +157,12 @@ Turbopack currently supports two strategies for assigning module IDs: If not set, Turbopack will use `'named'` for development builds and `'deterministic'` for production builds. -To configure the module IDs strategy, use the `moduleIdStrategy` field in `next.config.js`: +To configure the module IDs strategy, use the `moduleIds` field in `next.config.js`: ```js filename="next.config.js" module.exports = { - experimental: { - turbo: { - moduleIdStrategy: 'deterministic', - }, + turbo: { + moduleIds: 'deterministic', }, } ``` diff --git a/docs/01-app/05-api-reference/08-turbopack.mdx b/docs/01-app/05-api-reference/08-turbopack.mdx index b86ee5b7282dc..9f26346e65b21 100644 --- a/docs/01-app/05-api-reference/08-turbopack.mdx +++ b/docs/01-app/05-api-reference/08-turbopack.mdx @@ -130,7 +130,7 @@ Turbopack can be configured via `next.config.js` (or `next.config.ts`) under the Create manual aliases (like `resolve.alias` in webpack). - **`resolveExtensions`** Change or extend file extensions for module resolution. -- **`moduleIdStrategy`** +- **`moduleIds`** Set how module IDs are generated (`'named'` vs `'deterministic'`). - **`treeShaking`** Enable or disable tree shaking in dev and future production builds. diff --git a/examples/with-react-native-web/next.config.js b/examples/with-react-native-web/next.config.js index 9a5cacc4efc12..02723cdcc9ef6 100644 --- a/examples/with-react-native-web/next.config.js +++ b/examples/with-react-native-web/next.config.js @@ -1,24 +1,22 @@ /** @type {import('next').NextConfig} */ module.exports = { - experimental: { - turbo: { - resolveAlias: { - "react-native": "react-native-web", - }, - resolveExtensions: [ - ".web.js", - ".web.jsx", - ".web.ts", - ".web.tsx", - ".mdx", - ".tsx", - ".ts", - ".jsx", - ".js", - ".mjs", - ".json", - ], + turbopack: { + resolveAlias: { + "react-native": "react-native-web", }, + resolveExtensions: [ + ".web.js", + ".web.jsx", + ".web.ts", + ".web.tsx", + ".mdx", + ".tsx", + ".ts", + ".jsx", + ".js", + ".mjs", + ".json", + ], }, webpack: (config) => { config.resolve.alias = { diff --git a/examples/with-turbopack-loaders/next.config.js b/examples/with-turbopack-loaders/next.config.js index 374601906c9a1..fe3e34ad458db 100644 --- a/examples/with-turbopack-loaders/next.config.js +++ b/examples/with-turbopack-loaders/next.config.js @@ -1,15 +1,13 @@ module.exports = { - experimental: { - turbo: { - rules: { - "*.react.svg": { - loaders: ["@svgr/webpack"], - as: "*.js", - }, - "*.styl": { - loaders: ["stylus-loader"], - as: "*.css", - }, + turbopack: { + rules: { + "*.react.svg": { + loaders: ["@svgr/webpack"], + as: "*.js", + }, + "*.styl": { + loaders: ["stylus-loader"], + as: "*.css", }, }, }, diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 805dec13f6a41..2abd408f87c4b 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -3756,9 +3756,14 @@ function warnAboutTurbopackBuilds(config?: NextConfigComplete) { `We don't recommend deploying mission-critical applications to production.` ) warningStr += - '\n\n- It is expected that your bundle size might be different from `next build` with webpack. This will be improved as we work towards stability.' + '\n\n- ' + + bold( + 'Turbopack currently always builds production sourcemaps for the browser. This will include project sourcecode if deployed to production.' + ) + warningStr += + '\n- It is expected that your bundle size might be different from `next build` with webpack. This will be improved as we work towards stability.' - if (!config?.experimental.turbo?.unstablePersistentCaching) { + if (!config?.experimental.turbopackPersistentCaching) { warningStr += '\n- This build is without disk caching; subsequent builds will become faster when disk caching becomes available.' } diff --git a/packages/next/src/build/swc/index.ts b/packages/next/src/build/swc/index.ts index 24092de0f7636..f868b6a994b60 100644 --- a/packages/next/src/build/swc/index.ts +++ b/packages/next/src/build/swc/index.ts @@ -10,10 +10,10 @@ import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile' import { downloadNativeNextSwc, downloadWasmSwc } from '../../lib/download-swc' import type { NextConfigComplete, - TurboLoaderItem, - TurboRuleConfigItem, - TurboRuleConfigItemOptions, - TurboRuleConfigItemOrShortcut, + TurbopackLoaderItem, + TurbopackRuleConfigItem, + TurbopackRuleConfigItemOptions, + TurbopackRuleConfigItemOrShortcut, } from '../../server/config-shared' import { isDeepStrictEqual } from 'util' import { @@ -786,24 +786,24 @@ function bindingToApi( if (reactCompilerOptions) { const ruleKeys = ['*.ts', '*.js', '*.jsx', '*.tsx'] if ( - Object.keys(nextConfig?.experimental?.turbo?.rules ?? []).some((key) => + Object.keys(nextConfig?.turbopack?.rules ?? []).some((key) => ruleKeys.includes(key) ) ) { Log.warn( - `The React Compiler cannot be enabled automatically because 'experimental.turbo' contains a rule for '*.ts', '*.js', '*.jsx', and '*.tsx'. Remove this rule, or add 'babel-loader' and 'babel-plugin-react-compiler' to the Turbopack configuration manually.` + `The React Compiler cannot be enabled automatically because 'turbopack.rules' contains a rule for '*.ts', '*.js', '*.jsx', and '*.tsx'. Remove this rule, or add 'babel-loader' and 'babel-plugin-react-compiler' to the Turbopack configuration manually.` ) } else { - if (!nextConfig.experimental.turbo) { - nextConfig.experimental.turbo = {} + if (!nextConfig.turbopack) { + nextConfig.turbopack = {} } - if (!nextConfig.experimental.turbo.rules) { - nextConfig.experimental.turbo.rules = {} + if (!nextConfig.turbopack.rules) { + nextConfig.turbopack.rules = {} } for (const key of ['*.ts', '*.js', '*.jsx', '*.tsx']) { - nextConfig.experimental.turbo.rules[key] = { + nextConfig.turbopack.rules[key] = { browser: { foreign: false, loaders: [ @@ -840,7 +840,7 @@ function bindingToApi( if (nextConfigSerializable.experimental?.turbo?.rules) { ensureLoadersHaveSerializableOptions( - nextConfigSerializable.experimental.turbo?.rules + nextConfigSerializable.turbopack?.rules ) } @@ -878,7 +878,7 @@ function bindingToApi( } function ensureLoadersHaveSerializableOptions( - turbopackRules: Record + turbopackRules: Record ) { for (const [glob, rule] of Object.entries(turbopackRules)) { if (Array.isArray(rule)) { @@ -888,10 +888,10 @@ function bindingToApi( } } - function checkConfigItem(rule: TurboRuleConfigItem, glob: string) { + function checkConfigItem(rule: TurbopackRuleConfigItem, glob: string) { if (!rule) return if ('loaders' in rule) { - checkLoaderItems((rule as TurboRuleConfigItemOptions).loaders, glob) + checkLoaderItems((rule as TurbopackRuleConfigItemOptions).loaders, glob) } else { for (const key in rule) { const inner = rule[key] @@ -902,7 +902,10 @@ function bindingToApi( } } - function checkLoaderItems(loaderItems: TurboLoaderItem[], glob: string) { + function checkLoaderItems( + loaderItems: TurbopackLoaderItem[], + glob: string + ) { for (const loaderItem of loaderItems) { if ( typeof loaderItem !== 'string' && diff --git a/packages/next/src/build/turbopack-build/impl.ts b/packages/next/src/build/turbopack-build/impl.ts index 8beb28868082b..d36347e854328 100644 --- a/packages/next/src/build/turbopack-build/impl.ts +++ b/packages/next/src/build/turbopack-build/impl.ts @@ -54,8 +54,7 @@ export async function turbopackBuild(): Promise<{ const project = await bindings.turbo.createProject( { projectPath: dir, - rootPath: - config.experimental?.turbo?.root || config.outputFileTracingRoot || dir, + rootPath: config.turbopack?.root || config.outputFileTracingRoot || dir, distDir, nextConfig: config, jsConfig: await getTurbopackJsConfig(dir, config), @@ -83,7 +82,7 @@ export async function turbopackBuild(): Promise<{ }, { persistentCaching, - memoryLimit: config.experimental.turbo?.memoryLimit, + memoryLimit: config.experimental?.turbopackMemoryLimit, dependencyTracking: persistentCaching, } ) diff --git a/packages/next/src/lib/turbopack-warning.ts b/packages/next/src/lib/turbopack-warning.ts index 0e8a2f11c2be7..981ade96cc3cb 100644 --- a/packages/next/src/lib/turbopack-warning.ts +++ b/packages/next/src/lib/turbopack-warning.ts @@ -144,7 +144,7 @@ export async function validateTurboNextConfig({ if (key.startsWith('webpack') && rawNextConfig.webpack) { hasWebpackConfig = true } - if (key.startsWith('experimental.turbo')) { + if (key.startsWith('turbopack')) { hasTurboConfig = true } diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 3424941a98364..1c241725af04c 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -7,10 +7,12 @@ import type zod from 'next/dist/compiled/zod' import type { SizeLimit } from '../types' import type { ExportPathMap, - TurboLoaderItem, - TurboRuleConfigItem, - TurboRuleConfigItemOptions, - TurboRuleConfigItemOrShortcut, + TurbopackLoaderItem, + DeprecatedExperimentalTurboOptions, + TurbopackOptions, + TurbopackRuleConfigItem, + TurbopackRuleConfigItemOptions, + TurbopackRuleConfigItemOrShortcut, } from './config-shared' import type { Header, @@ -99,7 +101,7 @@ const zHeader: zod.ZodType
= z.object({ internal: z.boolean().optional(), }) -const zTurboLoaderItem: zod.ZodType = z.union([ +const zTurboLoaderItem: zod.ZodType = z.union([ z.string(), z.object({ loader: z.string(), @@ -108,13 +110,13 @@ const zTurboLoaderItem: zod.ZodType = z.union([ }), ]) -const zTurboRuleConfigItemOptions: zod.ZodType = +const zTurboRuleConfigItemOptions: zod.ZodType = z.object({ loaders: z.array(zTurboLoaderItem), as: z.string().optional(), }) -const zTurboRuleConfigItem: zod.ZodType = z.union([ +const zTurboRuleConfigItem: zod.ZodType = z.union([ z.literal(false), z.record( z.string(), @@ -122,10 +124,50 @@ const zTurboRuleConfigItem: zod.ZodType = z.union([ ), zTurboRuleConfigItemOptions, ]) - -const zTurboRuleConfigItemOrShortcut: zod.ZodType = +const zTurboRuleConfigItemOrShortcut: zod.ZodType = z.union([z.array(zTurboLoaderItem), zTurboRuleConfigItem]) +const zTurbopackConfig: zod.ZodType = z.strictObject({ + rules: z.record(z.string(), zTurboRuleConfigItemOrShortcut).optional(), + resolveAlias: z + .record( + z.string(), + z.union([ + z.string(), + z.array(z.string()), + z.record(z.string(), z.union([z.string(), z.array(z.string())])), + ]) + ) + .optional(), + resolveExtensions: z.array(z.string()).optional(), + moduleIds: z.enum(['named', 'deterministic']).optional(), +}) + +// Same as zTurbopackConfig but with deprecated properties. Unfortunately, base +// properties are duplicated here as `ZodType`s do not export `extend()`. +const zDeprecatedExperimentalTurboConfig: zod.ZodType = + z.strictObject({ + loaders: z.record(z.string(), z.array(zTurboLoaderItem)).optional(), + rules: z.record(z.string(), zTurboRuleConfigItemOrShortcut).optional(), + resolveAlias: z + .record( + z.string(), + z.union([ + z.string(), + z.array(z.string()), + z.record(z.string(), z.union([z.string(), z.array(z.string())])), + ]) + ) + .optional(), + resolveExtensions: z.array(z.string()).optional(), + treeShaking: z.boolean().optional(), + persistentCaching: z.union([z.number(), z.literal(false)]).optional(), + memoryLimit: z.number().optional(), + moduleIds: z.enum(['named', 'deterministic']).optional(), + minify: z.boolean().optional(), + sourceMaps: z.boolean().optional(), + }) + export const configSchema: zod.ZodType = z.lazy(() => z.strictObject({ allowedDevOrigins: z.array(z.string()).optional(), @@ -392,36 +434,15 @@ export const configSchema: zod.ZodType = z.lazy(() => typedRoutes: z.boolean().optional(), webpackBuildWorker: z.boolean().optional(), webpackMemoryOptimizations: z.boolean().optional(), - turbo: z - .object({ - loaders: z.record(z.string(), z.array(zTurboLoaderItem)).optional(), - rules: z - .record(z.string(), zTurboRuleConfigItemOrShortcut) - .optional(), - resolveAlias: z - .record( - z.string(), - z.union([ - z.string(), - z.array(z.string()), - z.record( - z.string(), - z.union([z.string(), z.array(z.string())]) - ), - ]) - ) - .optional(), - resolveExtensions: z.array(z.string()).optional(), - treeShaking: z.boolean().optional(), - persistentCaching: z - .union([z.number(), z.literal(false)]) - .optional(), - memoryLimit: z.number().optional(), - moduleIdStrategy: z.enum(['named', 'deterministic']).optional(), - minify: z.boolean().optional(), - sourceMaps: z.boolean().optional(), - }) - .optional(), + /** + * @deprecated Use `config.turbopack` instead. + */ + turbo: zDeprecatedExperimentalTurboConfig.optional(), + turbopackMemoryLimit: z.number().optional(), + turbopackMinify: z.boolean().optional(), + turbopackPersistentCaching: z.boolean().optional(), + turbopackSourceMaps: z.boolean().optional(), + turbopackTreeShaking: z.boolean().optional(), optimizePackageImports: z.array(z.string()).optional(), optimizeServerReact: z.boolean().optional(), clientTraceMetadata: z.array(z.string()).optional(), @@ -658,6 +679,7 @@ export const configSchema: zod.ZodType = z.lazy(() => target: z.string().optional(), trailingSlash: z.boolean().optional(), transpilePackages: z.array(z.string()).optional(), + turbopack: zTurbopackConfig.optional(), typescript: z .strictObject({ ignoreBuildErrors: z.boolean().optional(), diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index f6a0f10c43c9b..58ef34ec59685 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -23,7 +23,7 @@ export type NextConfigComplete = Required & { // override NextConfigComplete.experimental.htmlLimitedBots to string // because it's not defined in NextConfigComplete.experimental htmlLimitedBots: string | undefined - experimental: ExperimentalConfig + experimental: Omit } export type I18NDomains = readonly DomainLocale[] @@ -94,7 +94,10 @@ type JSONValue = | JSONValue[] | { [k: string]: JSONValue } -export type TurboLoaderItem = +/** + * @deprecated Use `TurbopackRuleConfigItem` instead. + */ +export type TurbopackLoaderItem = | string | { loader: string @@ -102,21 +105,21 @@ export type TurboLoaderItem = options: Record } -export type TurboRuleConfigItemOrShortcut = - | TurboLoaderItem[] - | TurboRuleConfigItem +export type TurbopackRuleConfigItemOrShortcut = + | TurbopackLoaderItem[] + | TurbopackRuleConfigItem -export type TurboRuleConfigItemOptions = { - loaders: TurboLoaderItem[] +export type TurbopackRuleConfigItemOptions = { + loaders: TurbopackLoaderItem[] as?: string } -export type TurboRuleConfigItem = - | TurboRuleConfigItemOptions - | { [condition: string]: TurboRuleConfigItem } +export type TurbopackRuleConfigItem = + | TurbopackRuleConfigItemOptions + | { [condition: string]: TurbopackRuleConfigItem } | false -export interface ExperimentalTurboOptions { +export interface TurbopackOptions { /** * (`next --turbopack` only) A mapping of aliased imports to modules to load in their place. * @@ -139,50 +142,52 @@ export interface ExperimentalTurboOptions { * * @see [Turbopack Loaders](https://nextjs.org/docs/app/api-reference/next-config-js/turbo#webpack-loaders) */ - loaders?: Record + rules?: Record + + /** + * The module ID strategy to use for Turbopack. + * If not set, the default is `'named'` for development and `'deterministic'` + * for production. + */ + moduleIds?: 'named' | 'deterministic' + + /** + * This is the repo root usually and only files above this + * directory can be resolved by turbopack. + */ + root?: string +} +export interface DeprecatedExperimentalTurboOptions extends TurbopackOptions { /** * (`next --turbopack` only) A list of webpack loaders to apply when running with Turbopack. * + * @deprecated Use `rules` instead. * @see [Turbopack Loaders](https://nextjs.org/docs/app/api-reference/next-config-js/turbo#webpack-loaders) */ - rules?: Record + loaders?: Record /** * A target memory limit for turbo, in bytes. + * @deprecated Use `experimental.turbopackMemoryLimit` instead. */ memoryLimit?: number /** - * Enable persistent caching for the turbopack dev server and build. + * Enable minification. Defaults to true in build mode and false in dev mode. + * @deprecated Use `experimental.turbopackMinify` instead. */ - unstablePersistentCaching?: boolean + minify?: boolean /** * Enable tree shaking for the turbopack dev server and build. + * @deprecated Use `experimental.turbopackTreeShaking` instead. */ treeShaking?: boolean - /** - * The module ID strategy to use for Turbopack. - * If not set, the default is `'named'` for development and `'deterministic'` - * for production. - */ - moduleIdStrategy?: 'named' | 'deterministic' - - /** - * This is the repo root usually and only files above this - * directory can be resolved by turbopack. - */ - root?: string - - /** - * Enable minification. Defaults to true in build mode and false in dev mode. - */ - minify?: boolean - /** * Enable source maps. Defaults to true. + * @deprecated Use `experimental.turbopackSourceMaps` instead. */ sourceMaps?: boolean } @@ -377,7 +382,35 @@ export interface ExperimentalConfig { */ optimizeServerReact?: boolean - turbo?: ExperimentalTurboOptions + /** + * @deprecated Use `config.turbopack` instead. + */ + turbo?: DeprecatedExperimentalTurboOptions + + /** + * A target memory limit for turbo, in bytes. + */ + turbopackMemoryLimit?: number + + /** + * Enable minification. Defaults to true in build mode and false in dev mode. + */ + turbopackMinify?: boolean + + /** + * Enable persistent caching for the turbopack dev server and build. + */ + turbopackPersistentCaching?: boolean + + /** + * Enable source maps. Defaults to true. + */ + turbopackSourceMaps?: boolean + + /** + * Enable tree shaking for the turbopack dev server and build. + */ + turbopackTreeShaking?: boolean /** * For use with `@next/mdx`. Compile MDX files using the new Rust compiler. @@ -1017,6 +1050,11 @@ export interface NextConfig extends Record { */ transpilePackages?: string[] + /** + * Options for Turbopack. Temporarily also available as `experimental.turbo` for compatibility. + */ + turbopack?: TurbopackOptions + skipMiddlewareUrlNormalize?: boolean skipTrailingSlashRedirect?: boolean diff --git a/packages/next/src/server/config.test.ts b/packages/next/src/server/config.test.ts index c50a5d217be94..4ec04e82f896e 100644 --- a/packages/next/src/server/config.test.ts +++ b/packages/next/src/server/config.test.ts @@ -127,14 +127,12 @@ describe('loadConfig', () => { loadConfig('', __dirname, { customConfig: { experimental: { - turbo: { - unstablePersistentCaching: true, - }, + turbopackPersistentCaching: true, }, }, }) ).rejects.toThrow( - /The experimental feature "experimental.turbo.unstablePersistentCaching" can only be enabled when using the latest canary version of Next.js./ + /The experimental feature "experimental.turbopackPersistentCaching" can only be enabled when using the latest canary version of Next.js./ ) }) }) diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index bfa3b11bf7b3f..408de088c3e64 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -9,7 +9,7 @@ import type { ExperimentalConfig, NextConfigComplete, NextConfig, - TurboLoaderItem, + TurbopackLoaderItem, } from './config-shared' import { loadWebpackHook } from './config-utils' @@ -251,9 +251,9 @@ function assignDefaults( throw new CanaryOnlyError({ feature: 'experimental.ppr' }) } else if (result.experimental?.dynamicIO) { throw new CanaryOnlyError({ feature: 'experimental.dynamicIO' }) - } else if (result.experimental?.turbo?.unstablePersistentCaching) { + } else if (result.experimental?.turbopackPersistentCaching) { throw new CanaryOnlyError({ - feature: 'experimental.turbo.unstablePersistentCaching', + feature: 'experimental.turbopackPersistentCaching', }) } else if (result.experimental?.nodeMiddleware) { throw new CanaryOnlyError({ feature: 'experimental.nodeMiddleware' }) @@ -655,14 +655,11 @@ function assignDefaults( } } - if ( - result?.experimental?.turbo?.root && - !isAbsolute(result.experimental.turbo.root) - ) { - result.experimental.turbo.root = resolve(result.experimental.turbo.root) + if (result?.turbopack?.root && !isAbsolute(result.turbopack.root)) { + result.turbopack.root = resolve(result.turbopack.root) if (!silent) { Log.warn( - `experimental.turbo.root should be absolute, using: ${result.experimental.turbo.root}` + `turbopack.root should be absolute, using: ${result.turbopack.root}` ) } } @@ -672,27 +669,21 @@ function assignDefaults( result.deploymentId = process.env.NEXT_DEPLOYMENT_ID } - if (result?.outputFileTracingRoot && !result?.experimental?.turbo?.root) { - dset( - result, - ['experimental', 'turbo', 'root'], - result.outputFileTracingRoot - ) + if (result?.outputFileTracingRoot && !result?.turbopack?.root) { + dset(result, ['turbopack', 'root'], result.outputFileTracingRoot) } // use the closest lockfile as tracing root - if (!result?.outputFileTracingRoot || !result?.experimental?.turbo?.root) { + if (!result?.outputFileTracingRoot || !result?.turbopack?.root) { let rootDir = findRootDir(dir) if (rootDir) { if (!result?.outputFileTracingRoot) { result.outputFileTracingRoot = rootDir - defaultConfig.outputFileTracingRoot = result.outputFileTracingRoot } - if (!result?.experimental?.turbo?.root) { - dset(result, ['experimental', 'turbo', 'root'], rootDir) - dset(defaultConfig, ['experimental', 'turbo', 'root'], rootDir) + if (!result?.turbopack?.root) { + dset(result, ['turbopack', 'root'], rootDir) } } } @@ -1273,16 +1264,36 @@ export default async function loadConfig( 'See more info here https://nextjs.org/docs/app/api-reference/next-config-js/turbo' ) - const rules: Record = {} + const rules: Record = {} for (const [ext, loaders] of Object.entries( userConfig.experimental.turbo.loaders )) { - rules['*' + ext] = loaders as TurboLoaderItem[] + rules['*' + ext] = loaders as TurbopackLoaderItem[] } userConfig.experimental.turbo.rules = rules } + if (userConfig.experimental?.turbo) { + curLog.warn( + 'The config property `experimental.turbo` is deprecated. Move this setting to `config.turbopack` as Turbopack is now stable.' + ) + + // Merge the two configs, preferring values in `config.turbopack`. + userConfig.turbopack = { + ...userConfig.experimental.turbo, + ...userConfig.turbopack, + } + userConfig.experimental.turbopackMemoryLimit ??= + userConfig.experimental.turbo.memoryLimit + userConfig.experimental.turbopackMinify ??= + userConfig.experimental.turbo.minify + userConfig.experimental.turbopackTreeShaking ??= + userConfig.experimental.turbo.treeShaking + userConfig.experimental.turbopackSourceMaps ??= + userConfig.experimental.turbo.sourceMaps + } + if (userConfig.experimental?.useLightningcss) { const { loadBindings } = require('next/dist/build/swc') const isLightningSupported = (await loadBindings())?.css?.lightning diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index b29998c080cf2..33471bc56fa28 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -209,7 +209,7 @@ export async function createHotReloaderTurbopack( { projectPath: projectPath, rootPath: - opts.nextConfig.experimental.turbo?.root || + opts.nextConfig.turbopack?.root || opts.nextConfig.outputFileTracingRoot || projectPath, distDir, @@ -240,7 +240,7 @@ export async function createHotReloaderTurbopack( }, { persistentCaching: isPersistentCachingEnabled(opts.nextConfig), - memoryLimit: opts.nextConfig.experimental.turbo?.memoryLimit, + memoryLimit: opts.nextConfig.experimental?.turbopackMemoryLimit, } ) setBundlerFindSourceMapImplementation( diff --git a/packages/next/src/shared/lib/turbopack/utils.ts b/packages/next/src/shared/lib/turbopack/utils.ts index ecb50b5d09f6a..a27e0805fd170 100644 --- a/packages/next/src/shared/lib/turbopack/utils.ts +++ b/packages/next/src/shared/lib/turbopack/utils.ts @@ -262,5 +262,5 @@ export function renderStyledStringToErrorAnsi(string: StyledString): string { export function isPersistentCachingEnabled( config: NextConfigComplete ): boolean { - return config.experimental.turbo?.unstablePersistentCaching || false + return config.experimental?.turbopackPersistentCaching || false } diff --git a/test/e2e/app-dir/react-owner-stacks-svgr/next.config.js b/test/e2e/app-dir/react-owner-stacks-svgr/next.config.js index 78354ccc678f0..dcae192243b05 100644 --- a/test/e2e/app-dir/react-owner-stacks-svgr/next.config.js +++ b/test/e2e/app-dir/react-owner-stacks-svgr/next.config.js @@ -2,13 +2,11 @@ * @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - turbo: { - rules: { - '*.svg': { - loaders: ['@svgr/webpack'], - as: '*.js', - }, + turbopack: { + rules: { + '*.svg': { + loaders: ['@svgr/webpack'], + as: '*.js', }, }, }, diff --git a/test/e2e/app-dir/resolve-extensions/next.config.js b/test/e2e/app-dir/resolve-extensions/next.config.js index 12879af6917f2..86973b6a8ec43 100644 --- a/test/e2e/app-dir/resolve-extensions/next.config.js +++ b/test/e2e/app-dir/resolve-extensions/next.config.js @@ -4,10 +4,8 @@ const extensions = ['', '.png', '.tsx', '.ts', '.jsx', '.js', '.json'] * @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - turbo: { - resolveExtensions: [...extensions], - }, + turbopack: { + resolveExtensions: [...extensions], }, webpack(config) { config.resolve.extensions = [...extensions] diff --git a/test/e2e/app-dir/scss/npm-import-tilde/npm-import-tilde.test.ts b/test/e2e/app-dir/scss/npm-import-tilde/npm-import-tilde.test.ts index 510852564de39..78f6df4b5d7fd 100644 --- a/test/e2e/app-dir/scss/npm-import-tilde/npm-import-tilde.test.ts +++ b/test/e2e/app-dir/scss/npm-import-tilde/npm-import-tilde.test.ts @@ -3,12 +3,10 @@ import { nextTestSetup } from 'e2e-utils' const nextConfig = { - experimental: { - turbo: { - resolveAlias: { - '/*': './*', - '~*': '*', - }, + turbopack: { + resolveAlias: { + '/*': './*', + '~*': '*', }, }, } diff --git a/test/e2e/app-dir/turbopack-loader-resource-query/next.config.js b/test/e2e/app-dir/turbopack-loader-resource-query/next.config.js index 3721e2ce123e8..4692bd3a5bea8 100644 --- a/test/e2e/app-dir/turbopack-loader-resource-query/next.config.js +++ b/test/e2e/app-dir/turbopack-loader-resource-query/next.config.js @@ -1,12 +1,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - turbo: { - rules: { - '*.mdx': { - loaders: ['test-loader.js'], - as: '*.js', - }, + turbopack: { + rules: { + '*.mdx': { + loaders: ['test-loader.js'], + as: '*.js', }, }, }, diff --git a/test/e2e/app-dir/webpack-loader-conditions/next.config.js b/test/e2e/app-dir/webpack-loader-conditions/next.config.js index 3ea622b3ee9a9..fc11041f27a7e 100644 --- a/test/e2e/app-dir/webpack-loader-conditions/next.config.js +++ b/test/e2e/app-dir/webpack-loader-conditions/next.config.js @@ -2,38 +2,36 @@ * @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - turbo: { - rules: { - '*.test-file.js': { - browser: { - foreign: { - loaders: [ - { - loader: require.resolve('./test-file-loader.js'), - options: { browser: true, foreign: true }, - }, - ], - }, - default: { - loaders: [ - { - loader: require.resolve('./test-file-loader.js'), - options: { browser: true }, - }, - ], - }, + turbopack: { + rules: { + '*.test-file.js': { + browser: { + foreign: { + loaders: [ + { + loader: require.resolve('./test-file-loader.js'), + options: { browser: true, foreign: true }, + }, + ], }, - foreign: false, default: { loaders: [ { loader: require.resolve('./test-file-loader.js'), - options: { default: true }, + options: { browser: true }, }, ], }, }, + foreign: false, + default: { + loaders: [ + { + loader: require.resolve('./test-file-loader.js'), + options: { default: true }, + }, + ], + }, }, }, }, diff --git a/test/e2e/app-dir/webpack-loader-ts-transform/next.config.js b/test/e2e/app-dir/webpack-loader-ts-transform/next.config.js index 83ac0c531262d..aefc88dcf14f4 100644 --- a/test/e2e/app-dir/webpack-loader-ts-transform/next.config.js +++ b/test/e2e/app-dir/webpack-loader-ts-transform/next.config.js @@ -2,11 +2,9 @@ * @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - turbo: { - rules: { - '*.test-file.ts': [require.resolve('./test-file-loader.js')], - }, + turbopack: { + rules: { + '*.test-file.ts': [require.resolve('./test-file-loader.js')], }, }, } diff --git a/test/e2e/esm-externals/next.config.js b/test/e2e/esm-externals/next.config.js index 4e8f830721386..65375c5839713 100644 --- a/test/e2e/esm-externals/next.config.js +++ b/test/e2e/esm-externals/next.config.js @@ -1,9 +1,7 @@ module.exports = { - experimental: { - turbo: { - resolveAlias: { - 'preact/compat': 'react', - }, + turbopack: { + resolveAlias: { + 'preact/compat': 'react', }, }, serverExternalPackages: [ diff --git a/test/e2e/persistent-caching/next.config.js b/test/e2e/persistent-caching/next.config.js index 85d7aadbe8482..3e1c292598005 100644 --- a/test/e2e/persistent-caching/next.config.js +++ b/test/e2e/persistent-caching/next.config.js @@ -2,19 +2,19 @@ * @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - turbo: { - unstablePersistentCaching: true, - rules: { - 'app/page.tsx': { - loaders: ['./my-loader.js'], - }, - 'pages/pages.tsx': { - loaders: ['./my-loader.js'], - }, + turbopack: { + rules: { + 'app/page.tsx': { + loaders: ['./my-loader.js'], + }, + 'pages/pages.tsx': { + loaders: ['./my-loader.js'], }, }, }, + experimental: { + turbopackPersistentCaching: true, + }, webpack(config) { config.module.rules.push({ test: /app\/page\.tsx|pages\/pages.tsx/, diff --git a/test/e2e/turbopack-turbo-config-compatibility/app/layout.js b/test/e2e/turbopack-turbo-config-compatibility/app/layout.js new file mode 100644 index 0000000000000..4ee00a218505a --- /dev/null +++ b/test/e2e/turbopack-turbo-config-compatibility/app/layout.js @@ -0,0 +1,7 @@ +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/turbopack-turbo-config-compatibility/app/page.js b/test/e2e/turbopack-turbo-config-compatibility/app/page.js new file mode 100644 index 0000000000000..c5a59e9f9b32b --- /dev/null +++ b/test/e2e/turbopack-turbo-config-compatibility/app/page.js @@ -0,0 +1,5 @@ +import foo from 'foo' + +export default function Page() { + return
Hello {foo}
+} diff --git a/test/e2e/turbopack-turbo-config-compatibility/index.test.ts b/test/e2e/turbopack-turbo-config-compatibility/index.test.ts new file mode 100644 index 0000000000000..3d58d1c65d7ba --- /dev/null +++ b/test/e2e/turbopack-turbo-config-compatibility/index.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable jest/no-standalone-expect */ +import { nextTestSetup } from 'e2e-utils' +;(process.env.IS_TURBOPACK_TEST ? describe : describe.skip)( + 'turbopack-turbo-config-compatibility', + () => { + describe('including both turbopack and deprecated experimental turbo config', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('prefers turbopack config over deprecated experimental turbo config', async () => { + const $ = await next.render$('/') + expect($('#result').text()).toEqual('Hello turbopack') + }) + }) + + describe('only including deprecated experimental turbo config', () => { + const { next } = nextTestSetup({ + files: __dirname, + overrideFiles: { + 'next.config.js': `module.exports = { + experimental: { + turbo: { + resolveAlias: { + foo: './turbo.js', + }, + }, + }, + }`, + }, + }) + + it('still uses the deprecated experimental turbo config', async () => { + const $ = await next.render$('/') + expect($('#result').text()).toEqual('Hello turbo') + }) + }) + } +) diff --git a/test/e2e/turbopack-turbo-config-compatibility/next.config.js b/test/e2e/turbopack-turbo-config-compatibility/next.config.js new file mode 100644 index 0000000000000..8c1bfc417fd7f --- /dev/null +++ b/test/e2e/turbopack-turbo-config-compatibility/next.config.js @@ -0,0 +1,14 @@ +module.exports = { + turbopack: { + resolveAlias: { + foo: './turbopack.js', + }, + }, + experimental: { + turbo: { + resolveAlias: { + foo: './turbo.js', + }, + }, + }, +} diff --git a/test/e2e/turbopack-turbo-config-compatibility/turbo.js b/test/e2e/turbopack-turbo-config-compatibility/turbo.js new file mode 100644 index 0000000000000..7554597e94c27 --- /dev/null +++ b/test/e2e/turbopack-turbo-config-compatibility/turbo.js @@ -0,0 +1 @@ +export default 'turbo' diff --git a/test/e2e/turbopack-turbo-config-compatibility/turbopack.js b/test/e2e/turbopack-turbo-config-compatibility/turbopack.js new file mode 100644 index 0000000000000..4f7ddaeb011a7 --- /dev/null +++ b/test/e2e/turbopack-turbo-config-compatibility/turbopack.js @@ -0,0 +1 @@ +export default 'turbopack' diff --git a/test/integration/module-id-strategies/next.config.js b/test/integration/module-id-strategies/next.config.js deleted file mode 100644 index c535948b13b6f..0000000000000 --- a/test/integration/module-id-strategies/next.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - bundlePagesRouterDependencies: true, - serverExternalPackages: ['opted-out-external-package'], - experimental: { - turbo: { - moduleIdStrategy: - process.env.NODE_ENV === 'production' ? 'deterministic' : undefined, - }, - }, -} diff --git a/test/integration/module-id-strategies/components/CustomComponent.tsx b/test/integration/module-ids/components/CustomComponent.tsx similarity index 100% rename from test/integration/module-id-strategies/components/CustomComponent.tsx rename to test/integration/module-ids/components/CustomComponent.tsx diff --git a/test/integration/module-id-strategies/module-with-long-name.js b/test/integration/module-ids/module-with-long-name.js similarity index 100% rename from test/integration/module-id-strategies/module-with-long-name.js rename to test/integration/module-ids/module-with-long-name.js diff --git a/test/integration/module-ids/next.config.js b/test/integration/module-ids/next.config.js new file mode 100644 index 0000000000000..40c2e5b7e65a1 --- /dev/null +++ b/test/integration/module-ids/next.config.js @@ -0,0 +1,4 @@ +module.exports = { + bundlePagesRouterDependencies: true, + serverExternalPackages: ['opted-out-external-package'], +} diff --git a/test/integration/module-id-strategies/node_modules/external-module-with-long-name.js b/test/integration/module-ids/node_modules/external-module-with-long-name.js similarity index 100% rename from test/integration/module-id-strategies/node_modules/external-module-with-long-name.js rename to test/integration/module-ids/node_modules/external-module-with-long-name.js diff --git a/test/integration/module-id-strategies/pages/index.js b/test/integration/module-ids/pages/index.js similarity index 100% rename from test/integration/module-id-strategies/pages/index.js rename to test/integration/module-ids/pages/index.js diff --git a/test/integration/module-id-strategies/test/index.test.js b/test/integration/module-ids/test/index.test.js similarity index 100% rename from test/integration/module-id-strategies/test/index.test.js rename to test/integration/module-ids/test/index.test.js diff --git a/test/integration/production-browser-sourcemaps/test/index.test.js b/test/integration/production-browser-sourcemaps/test/index.test.js index fbe96a0ee81d9..5dc43b69c029b 100644 --- a/test/integration/production-browser-sourcemaps/test/index.test.js +++ b/test/integration/production-browser-sourcemaps/test/index.test.js @@ -12,6 +12,7 @@ function runTests() { const jsFiles = browserFiles.filter( (file) => file.endsWith('.js') && file.includes('/pages/') ) + expect(jsFiles).not.toBeEmpty() jsFiles.forEach((file) => { expect(browserFiles.includes(`${file}.map`)).toBe(true) diff --git a/test/integration/telemetry/next.config.persistent-cache b/test/integration/telemetry/next.config.persistent-cache index 1f7cb91e334c5..bca7301e9495f 100644 --- a/test/integration/telemetry/next.config.persistent-cache +++ b/test/integration/telemetry/next.config.persistent-cache @@ -1,7 +1,5 @@ module.exports = { experimental: { - turbo: { - unstablePersistentCaching: true - } + turbopackPersistentCaching: true } -} \ No newline at end of file +} From 9cc611d48dbdd93682b4e373ccf9966fd6a79bae Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:08:21 +0200 Subject: [PATCH 2/2] Update crates/next-core/src/next_config.rs --- crates/next-core/src/next_config.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index 1636b6b3e4468..25f50ff6e6c7a 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -1507,7 +1507,8 @@ impl NextConfig { // Temporarily always enable client source maps as tests regress. // TODO: Respect both `self.experimental.turbopack_source_maps` and // `self.production_browser_source_maps` - Ok(Vc::cell(true)) + let source_maps = self.experimental.turbopack_source_maps; + Ok(Vc::cell(source_maps.unwrap_or(true))) } #[turbo_tasks::function]