Skip to content

fix(asset): preserve typeof when rewriting asset URL placeholder (fix #22304) - #22330

Closed
mvanhorn wants to merge 1 commit into
vitejs:mainfrom
mvanhorn:fix/22304-asset-typeof-precedence
Closed

fix(asset): preserve typeof when rewriting asset URL placeholder (fix #22304)#22330
mvanhorn wants to merge 1 commit into
vitejs:mainfrom
mvanhorn:fix/22304-asset-typeof-precedence

Conversation

@mvanhorn

Copy link
Copy Markdown

Description

Fixes #22304.

When renderAssetUrlInJS rewrites a __VITE_ASSET__ placeholder that sits inside a string literal, the bare "+runtime+" substitution closes the literal, concatenates the runtime expression, then reopens the literal. That works when the surrounding context is a binary expression like var x = "...", but breaks when the literal sits to the right of a unary operator. With a relative base, source like:

if (typeof "VITE_ASSET_HASH" === "string") { ... }

becomes:

if (typeof "" + new URL("./hero.png", import.meta.url).href + "" === "string") { ... }

JavaScript parses this as (typeof "") + url + "" because typeof binds tighter than +. The branch silently drops because the comparison never holds.

Fix

Expand the rewrite range to include the surrounding matching quotes (single, double, or template) and emit a parenthesized string expression (""+runtime+""). A leading typeof (or any unary operator) now applies to the whole concatenated string. Apply the same change to both the assetUrlRE and publicAssetUrlRE branches.

Non-string contexts (CSS-in-JS like var s = ".foo{background:url(__VITE_ASSET__abc__)}") are unchanged because the placeholder is not adjacent to matching quotes.

Tests

Added unit tests in packages/vite/src/node/__tests__/plugins/asset.spec.ts covering:

  • typeof "..." wrapping (the regression)
  • "...", '...', and `...` quote pairs
  • CSS-in-JS fallback (no surrounding quotes)
  • Mismatched surrounding quotes (no false-positive wrap)

pnpm run test-unit (783 tests) and pnpm run test-build assets (218 tests across the assets playgrounds, including relative-base) both pass.

…itejs#22304)

When the asset placeholder is wrapped in a string literal and the literal
sits to the right of a unary operator (typeof, void, !), the bare
`"+runtime+"` substitution produces output that parses as
`(typeof "") + runtime + ""` because typeof binds tighter than `+`. The
result is a string concatenation instead of `"string"`.

Expand the rewrite range to include the surrounding matching quotes and
emit a parenthesized string expression `(""+runtime+"")` so a leading
unary operator binds across the whole expression. Apply to both
`assetUrlRE` and `publicAssetUrlRE` branches in `renderAssetUrlInJS`.

Non-string contexts (CSS-in-JS like `url(__VITE_ASSET__abc__)`) keep the
original substitution.
@sapphi-red

Copy link
Copy Markdown
Member

I'm closing this PR as I've made a more robust solution in #22888.
Thanks for the PR.

@sapphi-red sapphi-red closed this Aug 4, 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.

incorrect asset path replacement when the path is passed to typeof and using relative base

2 participants