diff --git a/docs/02-app/02-api-reference/05-next-config-js/turbo.mdx b/docs/02-app/02-api-reference/05-next-config-js/turbo.mdx
index 09c3044d26b90..d752e14cbbdfb 100644
--- a/docs/02-app/02-api-reference/05-next-config-js/turbo.mdx
+++ b/docs/02-app/02-api-reference/05-next-config-js/turbo.mdx
@@ -18,9 +18,9 @@ To configure loaders, add the names of the loaders you've installed and any opti
module.exports = {
experimental: {
turbo: {
- loaders: {
+ rules: {
// Option format
- '.md': [
+ '*.md': [
{
loader: '@mdx-js/loader',
options: {
@@ -29,7 +29,7 @@ module.exports = {
},
],
// Option-less format
- '.mdx': ['@mdx-js/loader'],
+ '*.mdx': ['@mdx-js/loader'],
},
},
},
diff --git a/examples/with-turbopack-loaders/next.config.js b/examples/with-turbopack-loaders/next.config.js
index 4b89c10aa7529..5ba0898344f7c 100644
--- a/examples/with-turbopack-loaders/next.config.js
+++ b/examples/with-turbopack-loaders/next.config.js
@@ -1,8 +1,15 @@
module.exports = {
experimental: {
turbo: {
- loaders: {
- '.svg': ['@svgr/webpack'],
+ rules: {
+ '*.react.svg': {
+ loaders: ['@svgr/webpack'],
+ as: '*.js',
+ },
+ '*.styl': {
+ loaders: ['stylus-loader'],
+ as: '*.css',
+ },
},
},
},
diff --git a/examples/with-turbopack-loaders/package.json b/examples/with-turbopack-loaders/package.json
index c70cb28065381..f0590fa8ad5af 100644
--- a/examples/with-turbopack-loaders/package.json
+++ b/examples/with-turbopack-loaders/package.json
@@ -15,6 +15,8 @@
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
+ "stylus": "0.59.0",
+ "stylus-loader": "7.1.3",
"typescript": "^4.9.3"
}
}
diff --git a/examples/with-turbopack-loaders/pages/index.js b/examples/with-turbopack-loaders/pages/index.js
index 574261858ff39..cf2f400b782ae 100644
--- a/examples/with-turbopack-loaders/pages/index.js
+++ b/examples/with-turbopack-loaders/pages/index.js
@@ -1,4 +1,5 @@
-import Vercel from '../vercel.svg'
+import Vercel from '../vercel.react.svg'
+import '../styles.styl'
export default function Home() {
return
diff --git a/examples/with-turbopack-loaders/styles.styl b/examples/with-turbopack-loaders/styles.styl
new file mode 100644
index 0000000000000..9e8b98a7a5f09
--- /dev/null
+++ b/examples/with-turbopack-loaders/styles.styl
@@ -0,0 +1,2 @@
+body
+ background-color: blue
diff --git a/examples/with-turbopack-loaders/vercel.svg b/examples/with-turbopack-loaders/vercel.react.svg
similarity index 100%
rename from examples/with-turbopack-loaders/vercel.svg
rename to examples/with-turbopack-loaders/vercel.react.svg
diff --git a/packages/next-swc/crates/next-core/src/next_config.rs b/packages/next-swc/crates/next-core/src/next_config.rs
index b0521dc335a2e..6457c56693a6a 100644
--- a/packages/next-swc/crates/next-core/src/next_config.rs
+++ b/packages/next-swc/crates/next-core/src/next_config.rs
@@ -376,7 +376,7 @@ pub enum RemotePatternProtocal {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase")]
pub struct ExperimentalTurboConfig {
- /// This option has been replace by `rules`.
+ /// This option has been replaced by `rules`.
pub loaders: Option,
pub rules: Option>,
pub resolve_alias: Option>,
diff --git a/packages/next/src/build/swc/index.ts b/packages/next/src/build/swc/index.ts
index 80164d4baf03f..c5d1a4ec1a652 100644
--- a/packages/next/src/build/swc/index.ts
+++ b/packages/next/src/build/swc/index.ts
@@ -8,7 +8,7 @@ import { getParserOptions } from './options'
import { eventSwcLoadFailure } from '../../telemetry/events/swc-load-failure'
import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile'
import { downloadWasmSwc, downloadNativeNextSwc } from '../../lib/download-swc'
-import { NextConfigComplete, TurboLoaderItem } from '../../server/config-shared'
+import { NextConfigComplete, TurboRule } from '../../server/config-shared'
import { isDeepStrictEqual } from 'util'
const nextVersion = process.env.__NEXT_VERSION as string
@@ -948,10 +948,8 @@ function bindingToApi(binding: any, _wasm: boolean) {
nextConfigSerializable.exportPathMap = {}
nextConfigSerializable.webpack = nextConfig.webpack && {}
- if (nextConfig.experimental?.turbo?.loaders) {
- ensureLoadersHaveSerializableOptions(
- nextConfig.experimental.turbo.loaders
- )
+ if (nextConfig.experimental?.turbo?.rules) {
+ ensureLoadersHaveSerializableOptions(nextConfig.experimental.turbo?.rules)
}
nextConfigSerializable.modularizeImports =
@@ -979,16 +977,17 @@ function bindingToApi(binding: any, _wasm: boolean) {
}
function ensureLoadersHaveSerializableOptions(
- turbopackLoaders: Record
+ turbopackRules: Record
) {
- for (const [ext, loaderItems] of Object.entries(turbopackLoaders)) {
+ for (const [glob, rule] of Object.entries(turbopackRules)) {
+ const loaderItems = Array.isArray(rule) ? rule : rule.loaders
for (const loaderItem of loaderItems) {
if (
typeof loaderItem !== 'string' &&
!isDeepStrictEqual(loaderItem, JSON.parse(JSON.stringify(loaderItem)))
) {
throw new Error(
- `loader ${loaderItem.loader} for match "${ext}" does not have serializable options. Ensure that options passed are plain JavaScript objects and values.`
+ `loader ${loaderItem.loader} for match "${glob}" does not have serializable options. Ensure that options passed are plain JavaScript objects and values.`
)
}
}
diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts
index 5b7b5ae9f1b58..5518ea229175c 100644
--- a/packages/next/src/server/config-schema.ts
+++ b/packages/next/src/server/config-schema.ts
@@ -453,6 +453,9 @@ const configSchema = {
loaders: {
type: 'object',
},
+ rules: {
+ type: 'object',
+ },
resolveAlias: {
type: 'object',
},
diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts
index a44d576098143..edb1e775dc013 100644
--- a/packages/next/src/server/config-shared.ts
+++ b/packages/next/src/server/config-shared.ts
@@ -93,6 +93,13 @@ export type TurboLoaderItem =
options: Record
}
+export type TurboRule =
+ | TurboLoaderItem[]
+ | {
+ loaders: TurboLoaderItem[]
+ as: string
+ }
+
export interface ExperimentalTurboOptions {
/**
* (`next --turbo` only) A mapping of aliased imports to modules to load in their place.
@@ -110,6 +117,13 @@ export interface ExperimentalTurboOptions {
* @see [Turbopack Loaders](https://nextjs.org/docs/app/api-reference/next-config-js/turbo#webpack-loaders)
*/
loaders?: Record
+
+ /**
+ * (`next --turbo` only) A list of webpack loaders to apply when running with Turbopack.
+ *
+ * @see [Turbopack Loaders](https://nextjs.org/docs/app/api-reference/next-config-js/turbo#webpack-loaders)
+ */
+ rules?: Record
}
export interface WebpackConfigContext {
diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts
index 9c08a33db917e..9a593d95186a5 100644
--- a/packages/next/src/server/config.ts
+++ b/packages/next/src/server/config.ts
@@ -12,6 +12,7 @@ import {
NextConfigComplete,
validateConfig,
NextConfig,
+ TurboLoaderItem,
} from './config-shared'
import { loadWebpackHook } from './config-utils'
import { ImageConfig, imageConfigDefault } from '../shared/lib/image-config'
@@ -852,6 +853,27 @@ export default async function loadConfig(
: canonicalBase) || ''
}
+ if (
+ userConfig.experimental?.turbo?.loaders &&
+ !userConfig.experimental?.turbo?.rules
+ ) {
+ curLog.warn(
+ 'experimental.turbo.loaders is now deprecated. Please update next.config.js to use experimental.turbo.rules as soon as possible.\n' +
+ 'The new option is similar, but the key should be a glob instead of an extension.\n' +
+ 'Example: loaders: { ".mdx": ["mdx-loader"] } -> rules: { "*.mdx": ["mdx-loader"] }" }\n' +
+ 'See more info here https://nextjs.org/docs/app/api-reference/next-config-js/turbo'
+ )
+
+ const rules: Record = {}
+ for (const [ext, loaders] of Object.entries(
+ userConfig.experimental.turbo.loaders
+ )) {
+ rules['*' + ext] = loaders as TurboLoaderItem[]
+ }
+
+ userConfig.experimental.turbo.rules = rules
+ }
+
onLoadUserConfig?.(userConfig)
const completeConfig = assignDefaults(
dir,