Turbopack: Add new webpack loader rule/condition syntax in config (#82857)
# This PR:
- [x] Allow a condition to be specified inline within a `rule`
- [x] Support `all`/`any`/`not` for conditions
- [x] Simple flattening/optimization logic for the generated `RuleCondition` objects
- [x] Unit test coverage of flattening logic
- [x] Allow builtin conditions (see https://github.com/vercel/next.js/pull/82765) to be specified in condition objects, instead of requiring a separate syntax for them
- [x] Update regex serialization logic in `packages/next/src/build/swc/index.ts`
- [x] Make sure the updated syntax is fully reflected in the config schema and typedefs
- [x] Emit collectibles for configuration issues instead of hard-failing with anyhow
- [x] e2e test coverage
# Remaining Work for Subsequent PRs
- [ ] Remove the legacy `conditions` field from the `turbopack` config object
- [ ] Remove the legacy built-in condition object syntax from rules: https://github.com/vercel/next.js/pull/83068
- [ ] Allow array values in the `turbopack.rules` object: https://github.com/vercel/next.js/pull/83138
- [ ] Update user-facing documentation
This PR is the bulk of the work towards implementing the proposed new loader rule syntax:
https://www.notion.so/vercel/Turbopack-loader-rule-syntax-254e06b059c4809096d1e7c9afad278c
*(copied below)*
# Loader Syntax Proposal
## Today (documented)
[https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders](https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders)
```javascript
module.exports = {
turbopack: {
rules: {
// all matched rules apply, adding their loaders in order
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
}
```
## Today (undocumented) - User-defined conditions
You can define custom `#`-prefixed “conditions” that can act as filters for rules.
```javascript
module.exports = {
turbopack: {
rules: {
'#svg-file': {
loaders: ['@svgr/webpack'],
// `as` is incompatible! There's no obvious glob to match against!
},
},
conditions: {
"#svg-file": {
// this is an implicit "and"/"all": both path and content must match
path: '*.svg', // can also be a RegExp
content: /\<svg[^\>]*\>/, // can match the contents of the file!
},
},
},
}
```
## Today (undocumented) - Built-in Conditions
These use a different syntax, and are provided by Next.js / Turbopack (are not user-defined).
```javascript
module.exports = {
turbopack: {
rules: {
// This will match svg files in the project that aren't from `node_modules`
'*.svg': {
// These object properties are evaluated in order, and the first match
// used. The supported keys can be found here:
// https://github.com/vercel/next.js/pull/82765
foreign: false, // causes us to bail out if node_modules are matched
default: {
loaders: ['@svgr/webpack'],
as: '*.js',
}
},
},
},
}
```
## Proposed
- Remove (minor breaking change in Next 16, was undocumented) the `conditions` field from the `turbopack` config object. The benefit of a separate `conditions` field was that named conditions could be re-used across rules. But, there’s not much value in allowing condition re-use as the configuration file is JS and could implement re-use with local variables or functions.
- The separate `conditions` field was also incompatible with the `as` field.
- Add an optional `condition` field to each rule object.
- Rules must be matched by a glob first (can be `*`) before the condition is checked.
- This allows compatibility with `as`, since there’s always an obvious glob for that to match against.
- Maintain support for `path` and `content` properties in `condition` objects.
- These can be used with each other (as an implicit `all`)
- Extend `condition` object with `{all: [...]}`, `{any: [...]}` , and `{not: ...}`.
- Similar to webpack’s `{and: [...]}`, `{or: [...]}` and `{not: ...}` syntax: https://webpack.js.org/configuration/module/#condition
- Our underlying `RuleCondition` enum already supports this, it’s just a matter of translating it from the JS object.
- Allow conditions to be an object or a string. If it’s a string, require it to be a built-in condition (like `'foreign'` or `'browser'`).
- Allow values in the rules object to either be objects (what we support today) or an array of objects.
```javascript
module.exports = {
turbopack: {
rules: {
// all matched rules apply, adding their loaders in order
'*.svg': [
// this optionally can be an array of objects (shown here), or just an object
{
// this condition is matched after the glob
condition: {
all: [
{ not: 'foreign' }, // excludes `node_modules`
{ path: /app\/.*\.svg/, content: /\<svg[^\>]*\>/ },
],
},
loaders: ['@svgr/webpack'],
as: '*.js',
},
],
},
},
}
```