Skip to content

[Bug] /*#__PURE__*/ annotations placed inside parens cause Rolldown/Vite 8 [INVALID_ANNOTATION] warnings and disable tree-shaking #568

@hrithik-infinite

Description

@hrithik-infinite

Describe the bug

/* #__PURE__*/ annotations in the published ESM/ES5 bundles are positioned inside the wrapping parentheses, between the open paren and the annotated expression. Bundlers that follow the conventional rules for pure annotations (most notably Rolldown, used by Vite 8) cannot associate the annotation with the expression that follows it, so the annotation is silently ignored and dead-code elimination does not kick in for the affected values.

The pattern in the published artifact looks like this:

// node_modules/@nevware21/ts-utils/dist/es5/mod/ts-utils.js
var isNode = ( /*#__PURE__*/_getGlobalInstFn(function () {
    return !!( /*#__PURE__*/safe(function () { return (process && (process.versions || {}).node); }).v);
}));
var isWebWorker = ( /*#__PURE__*/_getGlobalInstFn(function () {
    return !!( /*#__PURE__*/safe(function () { return self && self instanceof WorkerGlobalScope; }).v);
}));

The expected pattern (as documented at https://rolldown.rs/in-depth/dead-code-elimination#pure) is that the annotation must come immediately before the call expression, with no intervening open paren:

var isNode = /*#__PURE__*/_getGlobalInstFn(function () {
    return !!(/*#__PURE__*/safe(function () { return (process && (process.versions || {}).node); }).v);
});

When Vite 8 (Rolldown) bundles a downstream app that pulls in @nevware21/ts-utils either directly or transitively (e.g. via @microsoft/applicationinsights-web, which is how I hit this), the build emits a series of [INVALID_ANNOTATION] warnings like:

[INVALID_ANNOTATION] A comment "/*#__PURE__*/" in
"node_modules/@nevware21/ts-utils/dist/es5/mod/ts-utils.js" contains
an annotation that Rolldown cannot interpret due to the position of the comment.
    ╭─[ node_modules/@nevware21/ts-utils/dist/es5/mod/ts-utils.js:920:16 ]
    │
920 │     return !!( /*#__PURE__*/safe(function () { return (process && (process.versions || {}).node); }).v);
    │                ──────┬──────
    │                      ╰──────── comment ignored due to position

grep -c "/\*[ ]*#__PURE__\*/" dist/es5/mod/ts-utils.js dist/es6/mod/ts-utils.js shows 159 occurrences in each build, so the issue is repo-wide rather than a single hand-written hotspot — it looks like a build-step / wrapper that prepends ( /*#__PURE__*/...) around each annotated value.

The build still produces working output; the symptom is silently lost dead-code elimination opportunities (slightly larger bundles and longer-than-needed module evaluation chains in downstream apps).

Runtime (please provide the browser and version, delete other browsers):

  • Node: 24 (downstream consumer); affects bundles targeting any browser/Node downstream

Desktop (please complete the following information):

  • OS: macOS 15 (Darwin 25.5.0)
  • Browser: n/a — issue surfaces at bundle time, not at runtime
  • Node: 24

To Reproduce

Steps to reproduce the behavior:

  1. Create a fresh project: npm create vite@latest pure-repro -- --template react-ts
  2. cd pure-repro && npm install
  3. npm install @nevware21/ts-utils@0.14.0
  4. Add a single import in src/main.tsx: import { isNode } from "@nevware21/ts-utils"; console.log(isNode());
  5. Run npm run build
  6. Observe multiple [INVALID_ANNOTATION] warnings in the build output pointing into node_modules/@nevware21/ts-utils/dist/es5/mod/ts-utils.js (the specific line/column varies based on which exports are pulled in).

The two warnings I see in my real-world build (Vite 8.0.14, Rolldown):

  • dist/es5/mod/ts-utils.js:920:16 — inside isNode
  • dist/es5/mod/ts-utils.js:923:16 — inside isWebWorker

@microsoft/applicationinsights-web (which depends on this package) triggers the same warnings via re-export, so any downstream that uses App Insights also hits the issue.

Expected behavior

/* #__PURE__*/ annotations in the published artifacts are placed immediately before the call expression they annotate, with no open paren between the annotation and the expression. Rolldown (and any other bundler that follows the conventional pure-annotation rules) is then able to mark the call as side-effect-free and tree-shake it when the result is unused.

Concretely: emit

var isNode = /*#__PURE__*/_getGlobalInstFn(function () { /* ... */ });

instead of

var isNode = ( /*#__PURE__*/_getGlobalInstFn(function () { /* ... */ }));

If the surrounding parens are required for some reason (e.g. a TypeScript downlevel artifact), move the annotation to sit between the open paren and the call without a leading space:

var isNode = (/*#__PURE__*/_getGlobalInstFn(function () { /* ... */ }));

In Rolldown's view, the key property is that the comment must be immediately adjacent to the call expression token; any whitespace/token in between (including the open paren) disqualifies the annotation.

Screenshots

n/a — terminal warnings only.

Additional context

  • Affected package version: @nevware21/ts-utils@0.14.0
  • Reproducer bundler: vite@8.0.14 (which uses Rolldown). Rollup tolerated the misplaced annotations silently, which is likely why this hasn't surfaced before.
  • Both the dist/es5/mod/ts-utils.js and dist/es6/mod/ts-utils.js artifacts are affected (159 occurrences in each). The pattern is uniform across the file, suggesting the upstream cause is a build-step wrapper rather than the source files themselves.
  • Rolldown documentation describing the placement rule: https://rolldown.rs/in-depth/dead-code-elimination#pure
  • Companion / downstream impact: @microsoft/applicationinsights-web and @microsoft/applicationinsights-core-js re-package some of these annotations and produce the same warnings via their own builds; happy to file a sibling issue on the ApplicationInsights-JS repo once this one has been confirmed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions