Fix #__PURE__ annotations being ignored by Rolldown/Vite 8 (#568)#569
Conversation
Remove the redundant surrounding parens from `(/*#__PURE__*/foo())` patterns. TypeScript inserts a space between the opening `(` and the leading comment when emitting, producing `( /*#__PURE__*/foo())` in the published bundles. Rolldown (used by Vite 8) cannot associate the annotation with the call when the comment is positioned between the open paren and the expression, so the annotation is silently ignored and the call is no longer tree-shaken. Removing the wrapping parens lets TypeScript emit `/*#__PURE__*/ foo()`, which Rolldown interprets correctly. Patterns where the parens are required for a top-level `as` cast are left untouched, because TypeScript already strips both the parens and the cast from the emit, producing a clean annotation. For the `!!(/*#__PURE__*/foo())` callers in `hasDocument`/`hasWindow`/ `hasNavigator`/`hasHistory`, removing the parens places the comment adjacent to `!!`, which TypeScript then drops. The inner annotations were redundant — the parent functions are already marked `/*#__NO_SIDE_EFFECTS__*/`, so callers can drop the calls when the result is unused. Verified the published bundles now contain zero `( /*#__PURE__*/` occurrences (was 159 in v0.14.0); 153 `#__PURE__` annotations are still emitted in valid positions. All node tests pass (1361 es5 + 1371 esnext).
|
Heads up — I'm also seeing 13 Let me know which you'd prefer:
Either is fine with me. |
The original PR removed the wrapping parens around all /*#__PURE__*/ annotations to eliminate Rolldown [INVALID_ANNOTATION] warnings. That worked for ~150 sites but rollup-plugin-cleanup silently dropped the comment in 8 `return !!fn()` sites where the comment was no longer adjacent to a parenthesized expression. On inspection those 8 annotations were already ineffective: - 6 sit inside functions marked with the outer /*#__NO_SIDE_EFFECTS__*/ pragma (hasDocument, hasWindow, hasNavigator, hasHistory, hasSymbol, hasIdleCallback). The outer annotation already tells the bundler the whole function call is side-effect-free, making the inner PURE redundant. - 2 sit inside lambda bodies whose outer _getGlobalInstFn(...) call already carries its own PURE (isNode, isWebWorker). The inner PURE only fires when the const is invoked, and the result is consumed by !!safe().v, so PURE has no tree-shaking effect there. Removing them at source keeps the bundle output Rolldown-clean (0 [INVALID_ANNOTATION] warnings) without reintroducing parens that rollup-plugin-cleanup would space-pad back into the warning shape. Minified bundle sizes change by +3 bytes; tree-shaking is unchanged.
|
@nev21 — full empirical verification (bundle sizes, tl;dr: tree-shaking is intact (minified bundles ±3 bytes), 0 |
…tion Adopts the maintainer's preferred approach: keep the wrapping parens on `(<comment>fn())` sites (and the _pureRef/_pureAssign helpers) so tree-shaking still works on older Rollup/Webpack/Terser toolchains that only honor the annotation when it hugs the call, and instead fix the Rolldown/Vite 8 [INVALID_ANNOTATION] warnings by normalizing the emitted spacing at the build layer rather than stripping parens from source. Source: reverts the paren-stripping (8a830ba); parens are restored everywhere. The only remaining source change vs the release is the 8 redundant inner annotations dropped in 88427b6 (the parent fns are already NO_SIDE_EFFECTS, so the inner PURE never affected DCE) - those stay removed. Build: adds a `fixPureAnnotations` renderChunk plugin to lib/rollup.config.js, wired into all three bundle factories and run last (after uglify on minified targets). It rewrites the spaced annotation forms emitted by TypeScript - an open paren followed by whitespace then the comment, plus the inner-space and at-sign variants - back to the canonical flush-against-paren form every bundler accepts. Uses magic-string so sourcemaps stay valid (added to devDependencies; already present transitively via @rollup/plugin-commonjs). Verified: rebuilt non-minified dist/ and bundle/ contain 0 spaced occurrences (was 152 pre-bundle), 167 canonical paren-wrapped, 178 total annotations; minified bundles strip annotations after DCE as before; config loads with no annotation warnings. 1361 es5 + 1371 esnext node tests pass; all size budgets pass.
…used vars Removes six genuinely unreferenced imports (getLength in drop_while/rotate/ take_while, UNDEFINED in fill, normalizeJsName in unique, isNullOrUndefined in copy) and adds varsIgnorePattern "^_" to no-unused-vars, mirroring the existing argsIgnorePattern. This clears all 13 warnings without deleting the seven compile-time type-assertion aliases in types.test.ts - those `type _x = AssertTrue<IsEqual<...>>` declarations are the test (a wrong type fails to compile) and are unreferenced by design. Business Impact: Restores a clean, zero-warning lint baseline so real issues stand out in review, while preserving the type-inference test coverage for the public utility types (ValueOf, DeepPartial, DeepReadonly, etc.).
|
Pushed the reworked approach here, following your guidance on #568 — kept the parens + What changed
I then reproduced #568 exactly (Vite 8.0.14 / Rolldown 1.0.2, published 0.14.0,
Verification: published 0.14.0 → 2 warnings; this branch → 0 warnings; 1361 es5 + 1371 esnext tests pass; all size budgets pass; config loads with no annotation warnings. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #569 +/- ##
=======================================
Coverage 99.42% 99.42%
=======================================
Files 158 158
Lines 4830 4830
Branches 1042 1039 -3
=======================================
Hits 4802 4802
Misses 28 28
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR addresses Rolldown/Vite 8 tree-shaking warnings and missed DCE by ensuring #__PURE__ annotations land in positions Rolldown accepts (notably normalizing parenthesized pure-call spacing), and includes a few related cleanups.
Changes:
- Add a Rollup post-processing plugin (
fixPureAnnotations) to canonicalize parenthesized#__PURE__annotation placement while preserving sourcemaps. - Remove redundant parenthesized pure-annotated boolean coercions in a few environment/timer helpers.
- Clean up unused imports and tweak ESLint unused-var rules to allow
_-prefixed intentionally-unused locals/types.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Adds magic-string to support code rewriting while preserving sourcemaps. |
| lib/rollup.config.js | Introduces and wires a Rollup plugin to normalize #__PURE__ annotation placement. |
| lib/src/helpers/environment.ts | Removes redundant (/*#__PURE__*/...) wrappers in !!... sites to avoid invalid annotation locations. |
| lib/src/timer/idle.ts | Removes redundant parenthesized pure annotation usage in hasIdleCallback(). |
| lib/src/symbol/symbol.ts | Removes redundant parenthesized pure annotation usage in hasSymbol(). |
| lib/src/object/copy.ts | Removes an unused import from helpers. |
| lib/src/array/unique.ts | Removes an unused import. |
| lib/src/array/take_while.ts | Removes an unused import. |
| lib/src/array/rotate.ts | Removes an unused import. |
| lib/src/array/drop_while.ts | Removes an unused import. |
| lib/src/array/fill.ts | Removes an unused constant import. |
| .eslintrc | Allows _-prefixed unused vars via varsIgnorePattern. |
Summary
Fixes #568 by making sure
#__PURE__annotations survive the build in a form that Rolldown/Vite 8 accepts, so tree-shaking works and[INVALID_ANNOTATION]warnings go away.Root cause
Some emitted chunks place
#__PURE__annotations inside parentheses in a way that current Rolldown does not recognize reliably. A few boolean helper call sites also carried redundant inner pure annotations that caused warnings in the!!(...)form.Fix
renderChunknormalization step that rewrites spaced/invalid pure-annotation forms into canonical/*#__PURE__*/placement in the final output.#__PURE__annotations from thehasDocument/hasWindow/hasNavigator/hasHistory/isNode/isWebWorkerboolean wrappers, since those functions are already marked#__NO_SIDE_EFFECTS__.Verification
#__PURE__annotations where expected.