Turbopack: avoid document-wide selector scans when probing for chunk tags (#100047)
## Summary
Fixes #99993.
On the first non-runtime load of each chunk URL, Turbopack's browser DOM
runtime checks whether a matching `<script>` or `<link rel=stylesheet>`
already exists. That check was a document-wide `querySelectorAll` with
four attribute and prefix selectors, so its cost grew with the size of
the whole DOM. It runs on the main thread during hydration, roughly once
per chunk. The selectors were also built by interpolating the URL
without escaping: a decoded `"` threw a `SyntaxError`, and a `\` missed
a tag that was already present.
The production loader (`doLoadChunk`) and the dev backend (`unloadChunk`
/ `reloadChunk`) now look tags up with `getElementsByTagName` and plain
string comparison. The helpers are defined once in the DOM backend and
reused by the dev backend, since the runtime files are concatenated
rather than imported as modules.
Matching behavior is otherwise unchanged:
- A tag matches if its raw attribute equals the chunk URL, or starts
with the URL plus `?`, for both the URL and its `decodeURI` form. A
different query string still does not match.
- The stylesheet lookup still requires `rel` to be exactly `stylesheet`
(ASCII case-insensitive). Dev CSS unload still ignores `rel`.
- All matching tags are returned in document order, from both head and
body.
- Results are snapshot arrays, so removing tags while looping over them
is safe.
This lowers the cost per probe but is still proportional to the number
of elements. A URL index that would make it independent of DOM size is
out of scope here.
## Verification
- New e2e test `test/e2e/app-dir/dynamic-chunk-dedupe`, passing with
`pnpm test-start-turbo` and `pnpm test-dev-turbo`:
- The fixture has server-rendered and `ssr: false` dynamic components,
including CSS modules, and sets a `deploymentId` so chunk URLs carry
`?dpl=`.
- It asserts that styles apply, there are no page errors, each chunk is
requested once, and the DOM has no duplicate chunk tags.
- Sanity check: with the lookup deliberately broken, the DOM check
failed on duplicate scripts and a duplicate stylesheet.
- `pnpm test-start-turbo
test/production/chunk-load-failure/chunk-load-failure.test.ts` (retry
path): 3/3
- `pnpm test-dev-turbo test/development/css-hmr-unmounted-chunk
test/development/app-dir/hmr-dynamic-component`: 7/7
- Snapshots regenerated with `UPDATE=1 cargo test -p turbopack-tests
--test snapshot`. Only the runtime change appears in the diff, and
132/132 pass on rerun.
- A Chromium harness compared the old selectors with the new lookups on
36 JS/CSS/unload cases. Results are identical except the `"` and `\`
cases, which now match literally.
- Sampled CPU profile on the issue's reproduction app at 4x CPU
throttling (median of 5): probe time drops from 106.9 to 40.8 ms at
about 4.3k elements, and from 215.9 to 51.6 ms at about 8.3k elements.
<!-- NEXT_JS_LLM -->
<!-- fleet 00169b97-050c-43e3-a74e-5c9141d0996e -->
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>