Skip to content

fix(new-url): keep the query and fragment when rewriting new URL() - #11420

Merged
hyfdev merged 4 commits into
rolldown:mainfrom
theRizwan:fix/new-url-keep-query-hash
Nov 3, 2026
Merged

hyfdev merged 4 commits into
rolldown:mainfrom
theRizwan:fix/new-url-keep-query-hash

Conversation

@theRizwan

Copy link
Copy Markdown
Contributor

Fixes case 3 of #11325. Case 1 is #11418 and case 2 is #11419; this one is independent of both.

Problem

With experimental.resolveNewUrlToAsset, the query and fragment were dropped when new URL() was rewritten:

new URL("./sprite.svg?v=1", import.meta.url)  // -> new URL("assets/sprite-[hash].svg", import.meta.url)
new URL("./sprite.svg#icon", import.meta.url) // -> new URL("assets/sprite-[hash].svg", import.meta.url)

so url.search and url.hash were lost, which breaks uses like SVG sprite fragments.

Fix

In the new URL() finalizer, after computing the asset path, append the part of the original specifier that follows the path (everything from the first ? or #, using clean_url). This matches Vite's vite:asset-import-meta-url, which keeps the postfix when it rewrites the URL. Resolution and the emitted asset are unchanged: all of ?v=1, #icon, ?v=1#icon and the plain path still share one emitted file.

Tests

New fixture topics/new_url/keep_query_and_hash covering a query, a fragment, both, and the plain path. With snapshot updates disabled (INSTA_UPDATE=no CI=true) it fails on main and passes here. No existing snapshot changed.

cargo test -p rolldown passes except test262_module_code, which fails the same way on main locally because the test262 submodule is not checked out. cargo fmt --check and cargo clippy -p rolldown --all-targets are clean.

AI disclosure: I used Claude Code to help trace the code path and write the change and test. I reviewed the change and ran the tests above myself.

`new URL("./a.txt?v=1", import.meta.url)` and `new URL("./sprite.svg#icon",
import.meta.url)` were rewritten to the bare asset path, so `url.search` and
`url.hash` were lost. Append the part of the original specifier after the
path to the rewritten asset path.

Refs rolldown#11325
@theRizwan
theRizwan marked this pull request as ready for review November 1, 2026 13:05
@codspeed

codspeed Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 4 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 7 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing theRizwan:fix/new-url-keep-query-hash (5bd1978) with main (3ebda9a)

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@hyfdev hyfdev self-assigned this Nov 2, 2026
@netlify

netlify Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit 5bd1978
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6ae93899a12ebf3d6f98786c
😎 Deploy Preview https://deploy-preview-11420--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@hyfdev hyfdev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

@hyfdev
hyfdev merged commit fa2038a into rolldown:main Nov 3, 2026
36 checks passed
@rolldown-guard rolldown-guard Bot mentioned this pull request Nov 5, 2026
shulaoda added a commit that referenced this pull request Nov 5, 2026
> [!IMPORTANT]
> **This is a minor release.** It adds experimental support for import attributes, and some changes alter default behavior compared to `1.2.16`. Please read **Breaking changes** before upgrading.

> [!WARNING]
> **Vite users:** Please do not force `[email protected]` into Vite with `overrides` or `resolutions` yet. Vite 8.3.6 and earlier need changes to work with it. Vite pins its `rolldown` version, so a normal install is not affected. Please wait for a Vite release that supports Rolldown 1.3.

## 🌟 Import attributes support (experimental)

Import attributes are the `with { ... }` part of an import. Node.js and browsers require them for JSON and CSS module scripts, so they are common in code written for these runtimes.

```js
import config from './config.json' with { type: 'json' };
import shader from './shader.glsl' with { type: 'text' };
import wasm from './module.wasm' with { type: 'bytes' };
```

With `experimental.importAttributes`:

- The same file imported with different attributes becomes different modules.
- `type: 'json'`, `'text'` and `'bytes'` work without a plugin.
- Plugins can read the attributes in `resolveId`, `load` and `transform`, and change them in `resolveId`.

```js
// rolldown.config.js
export default {
  experimental: {
    importAttributes: true,
  },
};
```

The design follows Rollup 5. The option is off by default, because it changes the id of modules imported with attributes, and plugins that use the id as a file path can break.

Rolldown's dev mode and Vite do not support it yet. See the [Import Attributes guide](https://rolldown.rs/in-depth/import-attributes) for details and plugin migration.

## 🌟 Agent-friendly build report (experimental)

With `devtools: { mode: 'report' }`, Rolldown writes small Markdown and JSON files about each build to `node_modules/.rolldown/report/`. A coding agent can read the numbers directly, and does not need scripts to parse a large event log. People and CI can use them too.

```js
// rolldown.config.js
export default {
  devtools: { mode: 'report' },
};
```

The report shows:

- **Time:** scan and generate times, plugin timings for each hook, hooks that did no work, and the slowest modules in `transform` hooks.
- **Chunks:** the largest chunks, the reason each chunk exists, and modules in more than one chunk.
- **Modules:** import kinds, the most-imported modules, and modules that several entries share.
- **Packages:** the largest packages, direct and transitive dependencies, and packages with more than one version.
- **Graph:** the initial load of each entry, the chunk import graph, and the retained size of each module.
- **Changes:** the difference from the previous build, or from a baseline that you pin.
- **Raw data:** stable metric ids in `metrics.json`, and complete graphs in `module-graph.json` and `chunk-graph.json`.

See the [Build Report guide](https://rolldown.rs/in-depth/build-report) for how to read each file and how to use the numbers in CI.

## 🌟 `@rolldown/perf-kit` (experimental)

`@rolldown/perf-kit` 0.1.0 is a new package that consumes the data from `devtools: { mode: 'report' }` and adds its own measurements. An agent or a person can use the processed data to improve LCP, FCP and startup time. A fixed baseline and a check of the rendered output show which changes to keep.

```sh
npx @rolldown/perf-kit help
```

It measures 3 targets:

- A page: LCP and FCP in a throttled browser (`scan`). The build needs sourcemaps.
- A Node.js or Deno server: the time until the server is ready (`node-scan`).
- A Cloudflare Worker: the time in global scope (`worker-scan`).

See the [Perf Kit guide](https://rolldown.rs/in-depth/perf-kit) for the agent prompts and more details.

We compared an agent alone with an agent using perf-kit on 3 open-source Cloudflare Workers. The prompts, results and steps to reproduce are in [perf-kit-worker-startup](https://github.com/IWANABETHATGUY-reproduction/perf-kit-worker-startup).

<p>
  <img width="45%" alt="Worker startup per app" src="https://github.com/user-attachments/assets/e9549835-86f4-4be0-be03-b735b6688987" />
  <img width="53%" alt="Cost of one agent run" src="https://github.com/user-attachments/assets/39fa9d18-00a9-47e5-9e18-737503797fdd" />
</p>

## ⚠️ Breaking changes

### `preserveModules` file names now follow Rollup (#11410, #11413)

`[name]` now keeps non-JS extensions, so `data.json` becomes `data.json.js` instead of `data.js`, and `Comp.vue` becomes `Comp.vue.js` instead of `Comp.js`. Libraries that point `exports` at the old file names need to update them.

### CLI: `--input` cannot be used together with positional entries (#11445)

`rolldown 1.js --input 2.js` now fails. Use `rolldown 1.js 2.js` or `rolldown --input 1.js --input 2.js`.

## 🩹 Notable fixes

### External imports keep their attributes correctly (#11302, #11303)

This applies to every build, with or without `experimental.importAttributes`. Before, two imports of one external with different attributes became one module, and one import was lost. The legacy `assert` keyword was also copied to the output, which Node.js 22+ cannot load.

```js
// input (`ext` is external)
import 'ext';
import 'ext' with { type: 'json' };
import data from 'ext2' assert { type: 'json' };

// 1.2.16 output
import 'ext' with { type: 'json' };
import data from 'ext2' assert { type: 'json' };

// 1.3.0 output, with a DEPRECATED_IMPORT_ASSERTION warning
import 'ext';
import 'ext' with { type: 'json' };
import data from 'ext2' with { type: 'json' };
```

Plugins now see such an external with the attributes in its id, for example `ext?attributes=%7B%22type%22%3A%22json%22%7D` in `this.getModuleIds()`.

---

## [1.3.0] - 2026-11-05

### 💥 BREAKING CHANGES

- cli: reject `--input` together with positional entries like Rollup (#11445) by @IWANABETHATGUY

### 🚀 Features

- perf-kit: measure the deployed site and replay it for local builds (#11215) by @IWANABETHATGUY
- perf-kit: diagnose chunk loading before FCP and LCP (#11214) by @IWANABETHATGUY
- import-attributes: keep import attributes away from plugins without the option (#11476) by @shulaoda
- import-attributes: let a `type` import attribute beat `moduleTypes` in the asset and copy plugins (#11309) by @shulaoda
- import-attributes: pass import attributes to the `transform` hook (#11308) by @shulaoda
- import-attributes: pass import attributes to the `load` hook (#11307) by @shulaoda
- import-attributes: accept import attributes in `this.resolve` and `this.load` (#11306) by @shulaoda
- import-attributes: pass import attributes to the `resolveId` and `resolveDynamicImport` hooks (#11305) by @shulaoda
- import-attributes: expose `experimental.importAttributes` and the module id helpers to JS (#11304) by @shulaoda
- import-attributes: render the import attributes of externals from their module identity (#11303) by @shulaoda
- import-attributes: make import attributes part of module identity (#11302) by @shulaoda
- import-attributes: model import attributes and keep them on the import record (#11301) by @shulaoda
- expose notify APIs (#11380) by @sapphi-red
- devtools: list the hooks that did no work in the report (#11388) by @IWANABETHATGUY

### 🐛 Bug Fixes

- respect `NO_COLOR` in error and warning diagnostics (#11499) by @shulaoda
- rolldown_plugin_vite_import_glob: match negated `**` globs from the walk root (#11495) by @shulaoda
- lazy-barrel: report used lazy barrel uncertain bindings with a dedicated error (#11493) by @shulaoda
- avoid following output directory symlinks with `output.cleanDir` (#11494) by @sapphi-red
- rolldown_plugin_vite_dynamic_import_vars: handle CRLF line breaks in the static import prefix (#11492) by @shulaoda
- rolldown_plugin_vite_build_import_analysis: ensure unique preload markers (#11157) by @sapphi-red
- watch: match Rollup for BUNDLE_END output and multi-config watch options (#10348) by @Brooooooklyn
- rolldown_plugin_vite_dynamic_import_vars: escape import specifier (#11483) by @sapphi-red
- error: add id, plugin, and frame to native errors (#9979) by @raminjafary
- treeshaking: run a CommonJS module that is reached only through `export *` (#11423) by @magurotuna
- escape URL in sourceMappingURL (#11482) by @sapphi-red
- rolldown_plugin_vite_import_glob: escape import names (#11486) by @sapphi-red
- rolldown_plugin_esm_external_require: escape import specifiers (#11487) by @sapphi-red
- tree-shaking: drop unused imports of side-effect-free externals in ESM output (#11470) by @hyfdev
- lazy-barrel: treat bindings behind skipped lazy barrel records as unknown (#11448) by @shulaoda
- rolldown_plugin_vite_react_refresh_wrapper: escape import specifier (#11485) by @sapphi-red
- devtools: count only hook calls that ran in the report (#11468) by @IWANABETHATGUY
- rust: harden bundle close lifecycle (#11371) by @Brooooooklyn
- strict-execution-order: name shared CJS re-export bindings independently of load order (#11206) by @natew
- dev/lazy: don't proxy plugin-resolved externals (#11295) by @cestercian
- rolldown_plugin_vite_transform: report oxc transformer warnings as warnings (#11459) by @Dunqing
- dev: report a lazy-compile failure as diagnostics, not a formatted string (#10490) by @tbvjaos510
- binding: stop mishandling per-call data at the callable builtin plugin boundary (#10482) by @hyfdev
- dev/lazy: resolve `import()` of a CommonJS module to its ESM view (#11020) by @h-a-n-a
- dev/lazy: load a lazy route in one request and only as factories (#11000) by @h-a-n-a
- error when one module id resolves as both external and non-external (#11421) by @IWANABETHATGUY
- sourcemap: keep only traced sources and names in module sourcemaps (#11270) by @IWANABETHATGUY
- give module namespace objects a `null` prototype and make them non-extensible (#11428) by @IWANABETHATGUY
- watch: reset JS load promises before rebuilding (#11316) by @owjs3901
- new-url: keep the query and fragment when rewriting `new URL()` (#11420) by @theRizwan
- dev: don't panic when buildStart emits a chunk during a partial rebuild (#11322) by @RagedPanda
- treeshaking: drop an unused top-level `import()` of a side-effect-free module that exports `then` (#11409) by @hyfdev
- derive reference ids from stable seeds for deterministic builds (#10797) by @MarshallOfSound
- rolldown_plugin_vite_import_glob: resolve aliased globs without blocking a runtime worker (#11385) by @shulaoda
- preserve-modules: read the extension after `sanitizeFileName` like Rollup (#11413) by @IWANABETHATGUY
- rolldown_plugin_vite_dynamic_import_vars: await the resolver instead of blocking a runtime worker (#11386) by @shulaoda
- cli: preserve entry arguments after double dash (#11438) by @shuvroroy
- treeshaking: invalidate constants when exports is reassigned (#11430) by @shuvroroy
- plugin: queue onLog callbacks eagerly (#11374) by @Brooooooklyn
- avoid inferring constants from compound CommonJS exports (#11429) by @shuvroroy
- json: avoid shadowing JSON.parse with named exports (#11432) by @shuvroroy
- watcher: prioritize queued input during debounce (#11369) by @Brooooooklyn
- replace: preserve declarations after Unicode text (#11435) by @shuvroroy
- treeshaking: don't drop side effects that follow an `exports.x` write in the same CommonJS statement (#11408) by @hyfdev
- don't inline a CommonJS export that is assigned conditionally or also through `module.exports` (#11404) by @hyfdev
- json: preserve __proto__ as an own data property (#11367) by @shuvroroy
- tree-shaking: preserve dynamic import exports read through callback arguments (#11366) by @shuvroroy
- respect `externalLiveBindings` for star re-exports from externals in cjs (#11328) by @logaretm
- preserve-modules: keep non-JS extensions in [name] (#11410) by @cestercian
- treeshaking: don't tree-shake a statement that holds a CommonJS top-level `return` (#11402) by @hyfdev
- don't export JSON keys that the finalizer inlines away from a shared chunk (#11396) by @IWANABETHATGUY
- sourcemap: support toUrl without Buffer (#11370) by @Brooooooklyn
- handle nullish thrown values in error summary (#11373) by @Brooooooklyn
- don't inline a `var` that module-level code reads before its declaration (#11379) by @hyfdev
- treat a repeated `var` declaration as a reassignment (#11378) by @hyfdev

### 🚜 Refactor

- remove dead concatenated wrapped module code (#11453) by @Dunqing
- align watch options for the util function to dev mode (#11474) by @sapphi-red
- simplify splitting oxc diagnostics by severity (#11458) by @Dunqing
- rolldown: run deferred drops on a dedicated thread instead of rayon (#11372) by @Brooooooklyn
- align watch options for dev mode and build (#11446) by @sapphi-red

### 📚 Documentation

- add build report and perf-kit in-depth pages (#11480) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: describe how import.meta.glob follows file changes (#10573) by @shulaoda
- import-attributes: add an in-depth guide for import attributes (#11310) by @shulaoda
- import-attributes: correct comments and docs about import attributes (#11477) by @shulaoda
- rolldown: explain why drops are deferred and why `drain` waits for every bundler's drops (#11455) by @hyfdev

### ⚡ Performance

- rolldown_plugin_vite_dynamic_import_vars: avoid string allocation for `first_quasi` (#11484) by @sapphi-red
- node: read each rendered chunk's binding fields once (#11364) by @mattlewis92

### 🧪 Testing

- import-attributes: run the esbuild import attributes tests with the option on (#11472) by @shulaoda
- import-attributes: port the Rollup 5 import attributes tests (#11381) by @shulaoda
- rolldown_plugin_vite_import_glob: cover the glob matcher (#10572) by @shulaoda
- rolldown_plugin_vite_import_glob: cover import.meta.glob file creation and deletion (#10551) by @shulaoda

### ⚙️ Miscellaneous Tasks

- ai: harden rolldown REPL decode skill (#11491) by @sapphi-red
- deps: update rust crate testing_macros to v2 (#11439) by @renovate[bot]
- deps: update dependency @napi-rs/cli to v3.10.12 (#11463) by @renovate[bot]
- deps: update dependency vite-plus to v1.5.0 (#11475) by @renovate[bot]
- deps: update dependency glob to v14 [security] (#11462) by @renovate[bot]
- deps: upgrade oxc to 0.157.0 (#11460) by @camc314
- deps: update dependency @napi-rs/cli to v3.10.11 (#11461) by @renovate[bot]
- deps: update dependency @napi-rs/cli to v3.10.10 (#11454) by @renovate[bot]
- deps: update rollup submodule for tests to v4.67.4 (#11451) by @rolldown-guard[bot]
- deps: update test262 submodule for tests (#11452) by @rolldown-guard[bot]
- deps: update dependency rolldown-plugin-dts to ^0.29.0 (#11427) by @renovate[bot]
- deps: update rust crates (#11426) by @renovate[bot]
- deps: update npm packages (#11425) by @renovate[bot]
- deps: update rust crate napi to v3.16.1 (#11401) by @renovate[bot]
- release `@rolldown/perf-kit` independently (#11394) by @shulaoda
- deps: update napi to v3.16.0 (#11389) by @renovate[bot]
- drop outdated `CLAUDE.md` symlink note from `AGENTS.md` (#11384) by @iiio2

### ❤️ New Contributors

* @raminjafary made their first contribution in [#9979](#9979)
* @natew made their first contribution in [#11206](#11206)
* @cestercian made their first contribution in [#11295](#11295)
* @owjs3901 made their first contribution in [#11316](#11316)
* @theRizwan made their first contribution in [#11420](#11420)
* @RagedPanda made their first contribution in [#11322](#11322)

Co-authored-by: shulaoda <[email protected]>
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.

2 participants