Repository navigation
fix(new-url): keep the query and fragment when rewriting new URL() - #11420
Merged
Merged
Conversation
`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
marked this pull request as ready for review
November 1, 2026 13:05
theRizwan
requested review from
IWANABETHATGUY,
hyfdev and
shulaoda
as code owners
November 1, 2026 13:05
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
✅ Deploy Preview for rolldown-rs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Merged
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 whennew URL()was rewritten:so
url.searchandurl.hashwere 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#, usingclean_url). This matches Vite'svite: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#iconand the plain path still share one emitted file.Tests
New fixture
topics/new_url/keep_query_and_hashcovering a query, a fragment, both, and the plain path. With snapshot updates disabled (INSTA_UPDATE=no CI=true) it fails onmainand passes here. No existing snapshot changed.cargo test -p rolldownpasses excepttest262_module_code, which fails the same way onmainlocally because thetest262submodule is not checked out.cargo fmt --checkandcargo clippy -p rolldown --all-targetsare 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.