Skip to content

fix(css): preserve distinct media filenames for CSS url() assets#1725

Merged
james-elicx merged 4 commits into
mainfrom
fix/css-url-asset-media-filenames
Jun 3, 2026
Merged

fix(css): preserve distinct media filenames for CSS url() assets#1725
james-elicx merged 4 commits into
mainfrom
fix/css-url-asset-media-filenames

Conversation

@james-elicx

@james-elicx james-elicx commented Jun 3, 2026

Copy link
Copy Markdown
Member

Closes #1690
Closes #1703
Fixes #1450

Overview

Matches Next.js asset/resource behaviour for CSS url() dependencies: emit them as files under /_next/static/media/ (instead of inlining small ones as data: URLs), and keep byte-identical source files (e.g. dark.svg / dark2.svg) as distinct output files instead of letting Vite/Rolldown's content-based asset dedupe collapse them onto one filename.

This is a slimmer reimplementation of the approach explored in #1703 — same core mechanism and public surface, roughly half the restore-side code. Co-authored with @NathanDrake2406, whose #1703 established the approach.

Why

Next.js (webpack asset/resource) emits one output file per source file a stylesheet references, keyed by module identity. Vite/Rolldown instead dedupe emitted assets by content, so two byte-identical files collapse to a single output and every url() that referenced either source is rewritten to that one filename — the second filename disappears. There's no config switch to disable that dedupe (vitejs/vite#8632); the one native escape hatch is this.emitFile({ fileName }), whose explicit-fileName assets are never deduped.

Approach

Build-only, client-scoped (dev CSS and RSC/SSR asset handling are untouched):

  1. mark — during the client CSS transform, tag each relative asset url() with a private ?vinext_css_url_asset=<source-basename> query. This is the only durable carrier of per-reference provenance: once Rolldown dedupes, the bundle can't tell which url() came from which source, and originalFileNames records the set of sources but not the per-reference mapping.
  2. defaults — default the client build to assetsInlineLimit: 0 (user config still wins) and route assets to css/[name].[hash:8][extname] / media/[name].[hash:8][extname].
  3. restore — at generateBundle, read each marked reference's source basename back out; when it differs from the deduped output, emit a sibling file under that name via the fileName escape hatch and rewrite the reference. The marker is then stripped (also from CSS that Vite inlined into JS chunks).

Each reference resolves from its own marker, so split CSS chunks stay correct with no shared cursor or bundle-iteration-order dependence.

Relationship to #1703

Same mark/restore mechanism, same plugin names, same marker string, same assetsInlineLimit/assetFileNames config. The difference is the restore implementation: this drops #1703's AssetIndexes (4 collections), reserveBundleFileName collision loop, restoredFileNames cache, and multi-fallback findAssetForUrlPath / replaceUrlPathAssetFileName in favour of a single basename map + an emitted-set + one deriveSiblingFileName. A review concluded those guarded scenarios are unreachable given the sibling-filename construction (source-stem + the deduped asset's own content-hash + ext), so the result is functionally equivalent with ~245 fewer lines. The one cheap robustness item the review flagged (defensive decodeURIComponent) is included.

Tests

Ports the Next.js scss/url-global e2e into the committed pages-basic and app-basic fixtures (built in isolation, served via the prod server):

  • Pages Router — one stylesheet referencing two byte-identical svgs must emit & serve both dark.*.svg and dark2.*.svg; plus a cross-chunk provenance case (two routes each referencing one of the identical svgs).
  • App Router — client-component CSS module counterpart.
  • Unit — marker stripping from JS-inlined CSS (escaped-quote layout, query/fragment preservation) and build-only plugin metadata.

Validation

  • tests/css-url-assets.test.ts — 8/8
  • tests/build-optimization.test.ts — 80/80
  • Regression sweep that builds these fixtures: prerender (79), pages-router (268; one pre-existing dev-server flake unrelated to this change, reproduces on clean main), routing + route-sorting (151), check (105).
  • tsc clean for the changed files; formatter applied.

Note

Draft — opened for comparison/review against #1703.

Match Next.js `asset/resource` behaviour for CSS `url()` dependencies:
emit them as files under `/_next/static/media/` instead of inlining, and
keep byte-identical sources (e.g. `dark.svg` / `dark2.svg`) as separate
output files instead of letting Vite/Rolldown's content-based asset
dedupe collapse them onto one filename.

There is no config switch to disable that dedupe (vitejs/vite#8632). The
native escape hatch is `this.emitFile({ fileName })`, which is never
deduped. A build-only, client-scoped pair of plugins:

  - mark: during the client CSS transform, tag each relative asset
    `url()` with a private `?vinext_css_url_asset=<source-basename>`
    query — the only durable carrier of per-reference provenance, since
    once Rolldown dedupes, the bundle can't tell which reference came
    from which source.
  - restore: at `generateBundle`, read the marker back, emit a sibling
    file under the wanted source name via the `fileName` escape hatch,
    rewrite the reference, and strip the marker.

Each reference resolves from its own marker, so split CSS chunks stay
correct without a shared cursor or bundle-iteration-order dependence.

This is a slimmer reimplementation of the approach explored in #1703:
same mark/restore mechanism and public surface, but the restore side
drops the AssetIndexes machinery, filename-reservation loop, and
multi-fallback lookups in favour of a single basename map + emitted-set,
since the sibling-filename construction (source-stem + content-hash +
ext) makes those guarded cases unreachable.

Tests port the Next.js scss/url-global e2e into the committed
`pages-basic` and `app-basic` fixtures (built in isolation), covering
the single-stylesheet two-identical-urls case and cross-chunk provenance.

Co-authored-by: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com>
@pkg-pr-new

pkg-pr-new Bot commented Jun 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1725

commit: fd409cb

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
web preview production

Comment thread packages/vinext/src/build/client-build-config.ts Fixed
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — fix(css): preserve distinct media filenames for CSS url() assets

Verified locally on the PR head (d43c21b):

  • vp test run tests/css-url-assets.test.ts8/8 pass
  • vp test run tests/build-optimization.test.ts80/80 pass
  • vp check on both changed source files → format/lint/type clean
  • md5sum confirms dark.svg/dark2.svg are byte-identical in all four fixture copies — exactly the input that triggers Rolldown's content dedupe this PR repairs.

Overall this is a clean, well-scoped implementation. The mark/restore mechanism is the right approach given there's no config switch to disable Rolldown's content dedupe, and per-reference markers (vs. #1703's shared cursor / AssetIndexes) are genuinely simpler and correctly handle the split-chunk provenance case. Build-only + client-scoped is the correct boundary — dev needs no fix because Vite's dev middleware serves byte-identical sources at distinct URLs already, and RSC/SSR asset handling is untouched.

A few notes, none blocking.

Correctness / parity

  1. Sibling files reuse the deduped asset's content hash. deriveSiblingFileName swaps only the stem (dark.HASH.svgdark2.HASH.svg), so both files share one hash. This matches Next.js (webpack also content-hashes, so byte-identical sources get the same hash) — good. Worth noting explicitly since it means the two emitted files are byte-identical and hash-identical, differing only in stem.

  2. Global assetsInlineLimit: 0 affects all client assets, not just CSS url() deps. The PR title frames this as CSS url() parity, but the default also stops inlining JS-imported assets on the client. This is defensible (webpack asset/resource doesn't inline these either, and user config still wins), and the comment documents it — just flagging that the blast radius is broader than "CSS url()" alone. Confirm this is the intended parity choice.

Minor

  1. transform returns map: null with the comment "never shifts line/column positions". Appending ?vinext_css_url_asset=… inside url(...) does shift columns of subsequent tokens on that line. In practice this is harmless — the marker is stripped at generateBundle, so final output positions are restored, and CSS asset-url sourcemaps are low value — but the comment's justification is slightly inaccurate. Consider rewording to "marker is transient and stripped before final output" rather than claiming no position shift.

  2. assetsByBase keys by output basename across the whole bundle. Two non-CSS assets in different dirs sharing a basename would collide. With content-hashed names this is effectively unreachable, and CSS files are correctly excluded — fine to leave, but a one-line comment noting the basename-uniqueness assumption would help future readers.

  3. CodeQL ReDoS finding on client-build-config.ts:17 (/\/+$/) is a false positive: the pattern is anchored, single-char, and linear; assetsDir is build-time config, not uncontrolled runtime input. Safe to dismiss.

  4. vinext:client-css-url-assets-defaults (configEnvironment) is partially redundant with the explicit environments.client.build.assetsInlineLimit assignments. It's a harmless belt-and-suspenders carrier for the default client env, and applies the same value, so no behavioral issue — just noting the duplication in case it's worth consolidating.

Test coverage

The split-chunk test (css-url-split-a/-b referencing the two identical svgs from separate page chunks) is the strongest addition — it's precisely the per-reference-provenance scenario where a shared cursor would break, and it's good that the per-marker design is exercised here. Unit coverage of inlined-chunk marker stripping (escaped quotes, query/fragment preservation) is thorough.

LGTM pending confirmation on point (2).

@ask-bonk

ask-bonk Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #1725.

Summary

I reviewed the CSS url() asset dedup fix and validated it locally:

  • Tests pass: css-url-assets.test.ts 8/8, build-optimization.test.ts 80/80
  • Clean: vp check format/lint/type clean on both changed source files
  • Verified the test premise: md5sum confirms all four dark.svg/dark2.svg fixtures are byte-identical — the exact input that triggers Rolldown's content dedupe

Verdict: LGTM pending one confirmation. The mark/restore design is sound, the per-reference marker approach is genuinely simpler than #1703's shared-cursor scheme and correctly handles split-chunk provenance, and the build-only/client-scoped boundary is the right call (dev needs no fix; RSC/SSR untouched).

Findings (none blocking):

  1. Sibling files reuse the deduped hash (matches Next.js) — noted for clarity.
  2. Main point to confirm: global assetsInlineLimit: 0 affects all client assets, not just CSS url() deps — broader than the title implies, though defensible parity-wise.
  3. transform's map: null comment is slightly inaccurate (marking does shift columns; it's just transient/stripped).
  4. assetsByBase basename-collision assumption could use a comment.
  5. CodeQL ReDoS finding is a false positive (anchored, build-time input) — safe to dismiss.
  6. configEnvironment defaults plugin is harmless but partially redundant with explicit env config.

github run

…ify comments

- tests/css-url-assets.test.ts: merge the two `vite` imports into one
  (import/no-duplicates lint error that failed CI Check).
- client-build-config.ts: replace `/\/+$/` trailing-slash strip with a
  linear loop so CodeQL stops flagging a (false-positive) ReDoS.
- index.ts: reword the css-url mark transform's `map: null` comment — the
  marker shifts columns transiently but is stripped before final output.
- css-url-assets.ts: note the basename-uniqueness assumption in assetsByBase.

Addresses review feedback on #1725.

Co-authored-by: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com>
@james-elicx

Copy link
Copy Markdown
Member Author

Thanks for the thorough review! Pushed d2bf995 addressing the feedback, plus the two real CI failures. Point-by-point:

CI failures

  • Check (lint) — real, mine: import/no-duplicates (vite imported twice in the test). Merged into one import.
  • CodeQL ReDoS (point 5) — replaced the /\/+$/ trailing-slash strip in client-build-config.ts with a linear loop. Agreed it was a false positive (anchored, single-char, build-time input), but dropping the regex clears the alert and reads fine.
  • app-router.test.ts > App Router Static export (2 tests) — these are a flake, not caused by this PR. They pass deterministically locally both in isolation and running the whole App Router Static export describe together (5/5). The Element type is invalid … got: object lines in the report are ambient stderr from the error-boundary fixture routes (e.g. layout-metadata-error-with-boundary), which appear in passing runs too. The likely cause is cross-shard resource/port contention during static export under CI load. The re-run on d2bf995 should clear them.
  • pages-router.test.ts > … generates valid JavaScript for the server entry — pre-existing flake, unrelated to this PR: it's a dev-server (createServer + ssrLoadModule) test, and all of this PR's plugins are apply: "build" (never loaded in dev). Verified it reproduces identically on clean main with this PR's source reverted.

Review notes

  • (1) shared content hash — correct and intended; documented it's Next-parity (webpack also content-hashes byte-identical sources to one hash). No change.
  • (2) assetsInlineLimit: 0 blast radius — confirmed intended. Next's webpack asset/resource doesn't inline JS-imported client assets either, it's scoped to the client build only (RSC/SSR untouched), and an explicit user build.assetsInlineLimit still wins. The title says "CSS url()" because that's the failing parity case that motivated it, but client-asset parity generally is the goal.
  • (3) map: null comment — fixed; reworded to "the marker is transient and stripped before final output" rather than claiming no column shift.
  • (4) assetsByBase basename collision — added a one-line comment noting output basenames embed the content hash, so last-write-wins is safe.
  • (6) configEnvironment redundancy — kept intentionally: it's the carrier for the default client environment in the single-build path (where there's no injected environments.client block), and applies the identical value elsewhere, so it's harmless belt-and-suspenders rather than a behavioral duplicate. Happy to consolidate if you'd prefer.

The url() asset tests previously added routes to the shared app-basic /
pages-basic fixtures and built those whole apps in isolation. That made
the tests CPU-heavy and, run concurrently under CI, contended with
app-router.test.ts's static-export test enough to push it past its 30s
timeout (it passes comfortably locally and on main; the extra parallel
load was the trigger). The added app-basic route also slightly inflated
every app-basic-wide test (static export renders all routes).

Move the fixtures into dedicated minimal committed apps
(tests/fixtures/css-url-assets-{app,pages}) that build in ~1-2s. Same
observable contract (byte-identical dark.svg/dark2.svg referenced from
one stylesheet, plus the split-chunk provenance case), now isolated from
the heavyweight shared fixtures and fast enough not to contend.

Co-authored-by: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com>
@james-elicx

Copy link
Copy Markdown
Member Author

CI is green now. Two follow-up commits since the review:

  • d2bf995 — lint duplicate-import fix + CodeQL regex + review-comment wording (Check & CodeQL green).
  • 10fdf10bmoved the url() asset tests to dedicated minimal fixtures (tests/fixtures/css-url-assets-{app,pages}) instead of adding routes to the shared app-basic/pages-basic and building those whole apps in isolation.

On the integration-shard failures: they were flaky 30s timeouts on the heaviest shard (app-router.test.ts, ~150s), not a defect here. Evidence:

  • The failing set rotated across runs (App Router static export consistently, plus a varying third — the pages-router dev-server flake, then an unrelated ISR revalidateTag test). None touch CSS assets.
  • App Router Static export > exports static… runs in 4.6s locally (15% of the 30s budget); it only times out under CI contention.
  • A plain re-run of the failed jobs went green with no code change.

The dedicated-fixtures change still reduces the blast radius the right way: the tests no longer build the full app-basic/pages-basic (≈20–30s each) in parallel, and the extra app-basic route no longer inflates every app-basic-wide test. Same observable contract (byte-identical dark.svg/dark2.svg from one stylesheet + the split-chunk provenance case), committed real fixtures (not written at test time), now ~1–2s to build.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review

@ask-bonk

ask-bonk Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review

1 similar comment
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — fix(css): preserve distinct media filenames for CSS url() assets (re-review on 10fdf10)

Re-reviewed after the two follow-up commits (d2bf995 lint/ReDoS/wording, 10fdf10 dedicated fixtures). Verified locally on the PR head:

  • vp test run tests/css-url-assets.test.ts8/8 pass
  • vp test run tests/build-optimization.test.ts80/80 pass
  • vp check on all 4 changed source/test files → format/lint/type clean
  • md5sum confirms all four dark.svg/dark2.svg fixture copies are byte-identical (ed5a143…) — the exact input that triggers Rolldown's content dedupe.

The previous review's findings are all addressed: map: null comment reworded, assetsByBase basename-collision comment added, ReDoS regex replaced with a linear loop, duplicate vite import merged. The assetsInlineLimit: 0 blast radius and shared-hash sibling behaviour were confirmed as intended Next-parity choices. The new build-optimization.test.ts case that drives createBuilder and asserts user build.assetsInlineLimit wins across all three environments is a strong addition.

The dedicated-fixtures refactor is a clear improvement — the tests no longer build the full app-basic/pages-basic in isolation, and the fixtures are committed (not written at test time) with good explanatory comments tying each file back to the upstream scss/url-global case.

LGTM. One non-blocking nit below.

Minor (non-blocking)

Stale header comment in tests/css-url-assets.test.ts (lines 9–20). The file's top comment still describes the pre-10fdf10 layout:

these exercise plain CSS against the committed pages-basic / app-basic fixtures:

  • pages-basic: styles/css-url-global.module.css …
  • app-basic: app/nextjs-compat/css-test/url-assets …

Those paths no longer exist (tests/fixtures/pages-basic/styles/css-url-global.module.css and tests/fixtures/app-basic/app/nextjs-compat/css-test/url-assets are both gone). The tests now use the dedicated tests/fixtures/css-url-assets-{pages,app} fixtures, which the PAGES_CSS_FIXTURE/APP_CSS_FIXTURE constants and the in-body comment at lines 32–34 correctly describe. Worth updating the header block to match so future readers aren't sent looking for files that don't exist.

Notes verified during review

  • All callers of the changed getClientOutputConfigForVite / createClientOutputConfig signatures (now taking assetsDir) are updated consistently across index.ts (4 sites) and client-build-config.ts.
  • Nested css//media/ asset subtrees serve correctly: the prod server maps /_next/static/... 1:1 to disk, and the integration test fetches the emitted svg URLs and asserts 200 + image/svg+xml.
  • The marker is appended to existing query strings and stripped via dropMarker/stripChunkMarkers, preserving other params and fragments (covered by the unit tests). decodeURIComponentSafe guards against a stray % aborting the rewrite.
  • Build-only + client-scoped boundary holds: the three plugins are all apply: "build" and gated on this.environment?.name === "client", so dev CSS and RSC/SSR asset handling stay untouched.

The flaky integration-shard timeouts called out in the PR thread are unrelated to this change (the plugins never load in dev, and the failing tests don't touch CSS assets).

@ask-bonk

ask-bonk Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #1725.

Summary

Re-reviewed the CSS url() asset dedup fix on the current head (10fdf10) and validated locally:

  • Tests pass: css-url-assets.test.ts 8/8, build-optimization.test.ts 80/80
  • Clean: vp check format/lint/type clean on all 4 changed files
  • Verified premise: md5sum confirms all four dark.svg/dark2.svg fixtures are byte-identical (ed5a143…)

Verdict: LGTM with one non-blocking nit.

The prior review's six findings are all addressed (map comment reworded, basename-collision comment added, ReDoS regex replaced with a linear loop, duplicate import merged, blast-radius/shared-hash confirmed as intended Next-parity). The dedicated-fixtures refactor is a clear improvement, and the new createBuilder-driven test asserting user assetsInlineLimit wins across all three environments is a solid addition.

One nit found: The header comment block in tests/css-url-assets.test.ts (lines 9–20) is stale — it still describes the pre-10fdf10 pages-basic/app-basic layout and references styles/css-url-global.module.css and app/nextjs-compat/css-test/url-assets, both of which no longer exist (verified). The tests now use the dedicated css-url-assets-{pages,app} fixtures.

I also independently verified: all callers of the changed config signatures are updated, nested css//media/ subtrees serve correctly (prod server maps _next/static/ 1:1, integration test confirms 200 + correct content-type), and the build-only/client-scoped boundary holds across all three plugins.

github run

…ixtures

The file header still described the pre-refactor layout (pages-basic /
app-basic paths that no longer exist). Point it at the dedicated
css-url-assets-{pages,app} fixtures. Comment-only.

Co-authored-by: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com>
@james-elicx

Copy link
Copy Markdown
Member Author

Thanks for the re-review! Fixed the stale header comment in fd409cb2 — it now points at the dedicated css-url-assets-{pages,app} fixtures instead of the old pages-basic/app-basic paths.

(The remaining inline CodeQL annotation on client-build-config.ts:17 is from the pre-fix commit d43c21b; the regex was replaced with a linear loop in d2bf995 and CodeQL passes on the current head.)

@james-elicx james-elicx marked this pull request as ready for review June 3, 2026 17:04
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk re-review

1 similar comment
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk re-review

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — fix(css): preserve distinct media filenames for CSS url() assets (fd409cb2)

Re-reviewed after the header-comment fix (fd409cb2, comment-only). Verified locally on the PR head:

  • vp test run tests/css-url-assets.test.ts8/8 pass
  • vp test run tests/build-optimization.test.ts80/80 pass
  • vp check on all 4 changed source/test files → format/lint/type clean
  • md5sum confirms all four dark.svg/dark2.svg fixture copies are byte-identical (ed5a143…) — the exact input that triggers Rolldown's content dedupe.

Verdict: LGTM. The last review's single non-blocking nit is resolved.

The stale header nit is fixed

fd409cb2 rewrites the tests/css-url-assets.test.ts header block to describe the dedicated fixtures. I confirmed the new paths now exist and the dead ones are gone:

  • tests/fixtures/css-url-assets-pages/styles/{global,split-a,split-b}.module.css, dark.svg, dark2.svg
  • tests/fixtures/css-url-assets-app/app/page.module.css, dark.svg, dark2.svg
  • The old pages-basic/styles/css-url-global.module.css and app-basic/app/nextjs-compat/css-test/url-assets references are gone, and those paths no longer exist on disk.

The header, the PAGES_CSS_FIXTURE/APP_CSS_FIXTURE constants, and the in-body comment (lines 33-35) now agree. The commit touches only the comment block (8 insertions / 7 deletions, no logic change).

Full status of prior findings — all addressed

  1. Shared content hash on sibling files — confirmed intended Next-parity. No change needed.
  2. assetsInlineLimit: 0 blast radius — confirmed intended client-scoped parity; user config still wins (the build-optimization.test.ts case driving createBuilder asserts this across all three environments).
  3. map: null comment — reworded to "the marker is transient — it's stripped before final output", no longer claiming no column shift. (index.ts:2548)
  4. assetsByBase basename-collision — one-line comment added noting output basenames embed an 8-char content hash so last-write-wins is safe. (css-url-assets.ts:226-229)
  5. CodeQL ReDoS/\/+$/ replaced with a linear trailing-slash loop in client-build-config.ts:19-21. The remaining inline annotation is on the pre-fix commit d43c21b, not the current head.
  6. configEnvironment redundancy — confirmed as intentional belt-and-suspenders carrier for the single-build client env path; harmless.

Independently re-verified on this head

  • All callers of getClientOutputConfigForVite / createClientOutputConfig (now taking assetsDir) are updated consistently across index.ts (4 sites) and client-build-config.ts.
  • Build-only + client-scoped boundary holds: all three plugins are apply: "build" and gated on this.environment?.name === "client" — dev CSS and RSC/SSR asset handling stay untouched.
  • The split-chunk provenance test (/split-adark, /split-bdark2) still asserts distinct, served svgs from separate page chunks — the case the per-marker design exists for.
  • Nested media/ subtrees serve correctly: the Pages integration test fetches the emitted svg URLs and asserts 200 + image/svg+xml; the App Router test stats the files on disk.

Nothing new found. Good to merge once CI is green.

@ask-bonk

ask-bonk Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #1725.

Summary

Re-reviewed the CSS url() asset dedup fix on the current head (fd409cb2) and validated locally:

  • Tests pass: css-url-assets.test.ts 8/8, build-optimization.test.ts 80/80
  • Clean: vp check format/lint/type clean on all 4 changed files
  • Verified premise: md5sum confirms all four dark.svg/dark2.svg fixture copies are byte-identical (ed5a143…)

Verdict: LGTM — no remaining nits.

The only outstanding item from the last review (the stale test header comment) is fixed in fd409cb2, a comment-only change. I verified the new fixture paths exist, the old pages-basic/app-basic paths are gone, and the header now agrees with the PAGES_CSS_FIXTURE/APP_CSS_FIXTURE constants and in-body comments.

All six prior findings remain addressed (map comment reworded, basename-collision comment present, ReDoS regex replaced with a linear loop, blast-radius/shared-hash confirmed intended). I independently re-confirmed: changed config signatures updated at all call sites, the build-only/client-scoped boundary holds across all three plugins, and the split-chunk provenance test still exercises distinct served svgs. Nothing new found — good to merge once CI is green.

github run

@james-elicx james-elicx merged commit f8481ac into main Jun 3, 2026
56 of 59 checks passed
@james-elicx james-elicx deleted the fix/css-url-asset-media-filenames branch June 3, 2026 21:46
@github-actions github-actions Bot mentioned this pull request Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build: SCSS/CSS url(...) references inlined as data URIs instead of emitted to /_next/static/media/

2 participants