Skip to content

Commit 60969fc

Browse files
authored
feat(nextjs): Use not: foreign condition in turbopack loaders (#19502)
Just came across https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#advanced-webpack-loader-conditions when researching for another issue. We can match the turbopack loaders more strictly to only run on user code. Potentially reduces build times. Closes #19504 (added automatically)
1 parent cce0029 commit 60969fc

File tree

5 files changed

+139
-1
lines changed

5 files changed

+139
-1
lines changed

packages/nextjs/src/config/turbopack/generateValueInjectionRules.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as path from 'path';
22
import type { VercelCronsConfig } from '../../common/types';
33
import type { RouteManifest } from '../manifest/types';
44
import type { JSONValue, TurbopackMatcherWithRule } from '../types';
5-
import { getPackageModules } from '../util';
5+
import { getPackageModules, supportsTurbopackRuleCondition } from '../util';
66

77
/**
88
* Generate the value injection rules for client and server in turbopack config.
@@ -50,11 +50,16 @@ export function generateValueInjectionRules({
5050
serverValues = { ...serverValues, ...isomorphicValues };
5151
}
5252

53+
const hasConditionSupport = nextJsVersion ? supportsTurbopackRuleCondition(nextJsVersion) : false;
54+
5355
// Client value injection
5456
if (Object.keys(clientValues).length > 0) {
5557
rules.push({
5658
matcher: '**/instrumentation-client.*',
5759
rule: {
60+
// Only run on user code, not node_modules or Next.js internals
61+
// condition field is only supported in Next.js 16+
62+
...(hasConditionSupport ? { condition: { not: 'foreign' } } : {}),
5863
loaders: [
5964
{
6065
loader: path.resolve(__dirname, '..', 'loaders', 'valueInjectionLoader.js'),
@@ -72,6 +77,9 @@ export function generateValueInjectionRules({
7277
rules.push({
7378
matcher: '**/instrumentation.*',
7479
rule: {
80+
// Only run on user code, not node_modules or Next.js internals
81+
// condition field is only supported in Next.js 16+
82+
...(hasConditionSupport ? { condition: { not: 'foreign' } } : {}),
7583
loaders: [
7684
{
7785
loader: path.resolve(__dirname, '..', 'loaders', 'valueInjectionLoader.js'),

packages/nextjs/src/config/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,17 @@ type TurbopackRuleCondition = {
849849
path: string | RegExp;
850850
};
851851

852+
// Condition used to filter when a loader rule applies.
853+
// Supports built-in string conditions ('foreign', 'browser', 'development', 'production', 'node', 'edge-light')
854+
// and boolean operators matching the Turbopack advanced condition syntax.
855+
type TurbopackRuleConditionFilter =
856+
| string
857+
| { not: TurbopackRuleConditionFilter }
858+
| { all: TurbopackRuleConditionFilter[] }
859+
| { any: TurbopackRuleConditionFilter[] }
860+
| { path: string | RegExp }
861+
| { content: RegExp };
862+
852863
export type TurbopackRuleConfigItemOrShortcut = TurbopackLoaderItem[] | TurbopackRuleConfigItem;
853864

854865
export type TurbopackMatcherWithRule = {
@@ -859,6 +870,7 @@ export type TurbopackMatcherWithRule = {
859870
type TurbopackRuleConfigItemOptions = {
860871
loaders: TurbopackLoaderItem[];
861872
as?: string;
873+
condition?: TurbopackRuleConditionFilter;
862874
};
863875

864876
type TurbopackRuleConfigItem =

packages/nextjs/src/config/util.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ export function supportsProductionCompileHook(version: string): boolean {
6767
return false;
6868
}
6969

70+
/**
71+
* Checks if the current Next.js version supports the `condition` field in Turbopack rules.
72+
* This field was introduced in Next.js 16.
73+
*
74+
* @param version - version string to check.
75+
* @returns true if Next.js version is 16 or higher
76+
*/
77+
export function supportsTurbopackRuleCondition(version: string): boolean {
78+
if (!version) {
79+
return false;
80+
}
81+
82+
const { major } = parseSemver(version);
83+
84+
if (major === undefined) {
85+
return false;
86+
}
87+
88+
return major >= 16;
89+
}
90+
7091
/**
7192
* Checks if the current Next.js version supports native debug ids for turbopack.
7293
* This feature was first introduced in Next.js v15.6.0-canary.36 and marked stable in Next.js v16

packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,68 @@ describe('constructTurbopackConfig', () => {
874874
});
875875
});
876876

877+
describe('condition field version gating', () => {
878+
it('should include condition field for Next.js 16+', () => {
879+
const userNextConfig: NextConfigObject = {};
880+
881+
const result = constructTurbopackConfig({
882+
userNextConfig,
883+
nextJsVersion: '16.0.0',
884+
});
885+
886+
const serverRule = result.rules!['**/instrumentation.*'] as { condition?: unknown; loaders: unknown[] };
887+
expect(serverRule.condition).toEqual({ not: 'foreign' });
888+
});
889+
890+
it('should include condition field for Next.js 17+', () => {
891+
const userNextConfig: NextConfigObject = {};
892+
893+
const result = constructTurbopackConfig({
894+
userNextConfig,
895+
routeManifest: { dynamicRoutes: [], staticRoutes: [], isrRoutes: [] },
896+
nextJsVersion: '17.0.0',
897+
});
898+
899+
const clientRule = result.rules!['**/instrumentation-client.*'] as { condition?: unknown; loaders: unknown[] };
900+
const serverRule = result.rules!['**/instrumentation.*'] as { condition?: unknown; loaders: unknown[] };
901+
expect(clientRule.condition).toEqual({ not: 'foreign' });
902+
expect(serverRule.condition).toEqual({ not: 'foreign' });
903+
});
904+
905+
it('should not include condition field for Next.js 15.x', () => {
906+
const userNextConfig: NextConfigObject = {};
907+
908+
const result = constructTurbopackConfig({
909+
userNextConfig,
910+
nextJsVersion: '15.4.1',
911+
});
912+
913+
const serverRule = result.rules!['**/instrumentation.*'] as { condition?: unknown; loaders: unknown[] };
914+
expect(serverRule).not.toHaveProperty('condition');
915+
});
916+
917+
it('should not include condition field for Next.js 14.x', () => {
918+
const userNextConfig: NextConfigObject = {};
919+
920+
const result = constructTurbopackConfig({
921+
userNextConfig,
922+
nextJsVersion: '14.2.0',
923+
});
924+
925+
const serverRule = result.rules!['**/instrumentation.*'] as { condition?: unknown; loaders: unknown[] };
926+
expect(serverRule).not.toHaveProperty('condition');
927+
});
928+
929+
it('should not include condition field when nextJsVersion is undefined', () => {
930+
const userNextConfig: NextConfigObject = {};
931+
932+
const result = constructTurbopackConfig({ userNextConfig });
933+
934+
const serverRule = result.rules!['**/instrumentation.*'] as { condition?: unknown; loaders: unknown[] };
935+
expect(serverRule).not.toHaveProperty('condition');
936+
});
937+
});
938+
877939
describe('safelyAddTurbopackRule', () => {
878940
const mockRule = {
879941
loaders: [

packages/nextjs/test/config/util.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,39 @@ describe('util', () => {
334334
expect(util.detectActiveBundler()).toBe('webpack');
335335
});
336336
});
337+
338+
describe('supportsTurbopackRuleCondition', () => {
339+
describe('supported versions (returns true)', () => {
340+
it.each([
341+
['16.0.0', 'Next.js 16.0.0'],
342+
['16.1.0', 'Next.js 16.1.0'],
343+
['17.0.0', 'Next.js 17.0.0'],
344+
['20.0.0', 'Next.js 20.0.0'],
345+
])('returns true for %s (%s)', version => {
346+
expect(util.supportsTurbopackRuleCondition(version)).toBe(true);
347+
});
348+
});
349+
350+
describe('unsupported versions (returns false)', () => {
351+
it.each([
352+
['15.9.9', 'Next.js 15.9.9'],
353+
['15.4.1', 'Next.js 15.4.1 (min Turbopack version)'],
354+
['15.0.0', 'Next.js 15.0.0'],
355+
['14.2.0', 'Next.js 14.2.0'],
356+
['13.0.0', 'Next.js 13.0.0'],
357+
])('returns false for %s (%s)', version => {
358+
expect(util.supportsTurbopackRuleCondition(version)).toBe(false);
359+
});
360+
});
361+
362+
describe('edge cases', () => {
363+
it('returns false for empty string', () => {
364+
expect(util.supportsTurbopackRuleCondition('')).toBe(false);
365+
});
366+
367+
it('returns false for invalid version string', () => {
368+
expect(util.supportsTurbopackRuleCondition('invalid')).toBe(false);
369+
});
370+
});
371+
});
337372
});

0 commit comments

Comments
 (0)