Repository navigation
Turbopack silently downlevels to ES5 and expands \p{...} above its compat data
#98124
ejc3
started this conversation in
Turbopack Error Report
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
When the oldest browser in an app's resolved
browserslistis newer than every version inTurbopack's bundled compat data,
next buildemits the client bundle as ES5 andexpands
\p{...}property escapes into bracketed character classes. No warning appears in thebuild output.
The expansion is the part that breaks working code.
Which queries break
Measured on
16.3.3, which starts downlevelling at chrome 142. Onlybrowserslistvaried.browserslist\p{Script=Han}chrome 141chrome >= 123chrome >= 150last 2 Chrome versionslast 1 Chrome versionchrome 200MODERN_BROWSERSLIST_TARGETMODERN_BROWSERSLIST_TARGETThe oldest resolved browser decides, not the newest.
chrome >= 123is safe although itresolves up to 151.
chrome 200is safe for an unrelated reason. browserslist throwsUnknown version 200 of chrome,getSupportedBrowsers()catches it with a barecatch {}(
next/dist/build/get-supported-browsers.js), and you getMODERN_BROWSERSLIST_TARGET. Sochrome 999reproduces nothing.A raw
last N versionsresolves toie 11,bb 7,op_mini all, so its ES5 output is expectedand is not a repro of this.
Boundary by release
last 1 Chrome versionresolves tonext@16.3.3next@16.4.0-canary.10chrome 154On canary,
chrome 141throughchrome 153are kept at 566,060 bytes andchrome 154expandsto 607,866.
last 1 Chrome versionresolves to 151 there, so it starts breaking three Chromereleases from now with no change on the app's part. On
16.3.3it already resolves into therange that breaks.
Canary fails the same way at the first Chrome version past its own data, which is the case for
fixing the fallback rather than relying on the data staying ahead of Chrome.
Reproduction
app/page.tsx:Add a default
app/layout.tsx, atsconfig.json, an emptynext.config.ts, and set"browserslist": ["last 1 Chrome version"].Checks that give false negatives
Both of these report success while the bug is present:
grep -c 'class 'across all chunks. The keyword survives in framework chunks whateverhappened to the app's own code.
_classCallCheck. SWC inlines the guard instead of calling a named helper.Selecting "the page's chunk" is also unreliable; two chunks match
source=here, at 991 and112,594 bytes. Scan the whole directory:
The build that produced the 608,683-byte row above says nothing about the target:
The emitted page chunk, with both symptoms:
Both symptoms flip at the same boundary on both releases. I would look at the target fallback
before treating the regex and class transforms as separate bugs.
getSupportedBrowsers()passesthe requested version through as the
browserslistQueryproject option, so the wrong behaviourlooks like it is downstream of that handoff rather than in it.
Why the expansion breaks code
On
16.3.3the ES5 downlevel costs 42,626 bytes on this page, 566,057 to 608,683. On one realapplication, deleting
"browserslist": ["last 1 Chrome version"]cut client JS from 12,029,915to 10,820,082 bytes, so the downlevelled bundle was 11 percent larger.
The expansion changes meaning rather than size. Libraries splice the fragment into a larger
class, which brackets of its own make malformed.
@excalidraw/excalidrawdoes this:The compositions throw, including the one inside its own
getLineBreakRegexfallbackcatch,so the library's own fallback cannot catch it:
It throws the first time a line wraps.
Expected
version in that data. Today such a target gets the most conservative output Turbopack can
produce, which is the opposite of what the query asked for.
percent larger or a regex that throws the first time a line wraps, is a long way from the
cause.
(Chrome 64+, Safari 11.1+, Firefox 78+), all far below anything configured here.
If you take a fix, please put it in 16.3.x as well. That is the release where
last 1 Chrome versionalready resolves into the range that breaks.The workaround meanwhile is to delete the
browserslistkey, which falls back toMODERN_BROWSERSLIST_TARGET(chrome 111,edge 111,firefox 111,safari 16.4), below bothboundaries.
Environment
next@16.3.3andnext@16.4.0-canary.10, Turbopack, Node 22.16.0, Linux x64,caniuse-lite
1.0.30001810.16.4.0-canary.0does not reproduce at chrome 147, so the boundary differs between the 16.3.3tag and canary.0. I did not identify what moved it.
16.4.0-canary.10is the newest canary I tested..nextremoved first.output: 'standalone',productionBrowserSourceMaps: true, and anon-default
turbopack.root.All reactions