Skip to content

Declaring sideEffects causes Rollup to drop code with side effects #5538

@mbostock

Description

@mbostock

Rollup Version

v4.18.0

Operating System (or Browser)

macOS

Node Version (if applicable)

20.11.0

Link To Reproduction

https://github.com/mbostock/rollup-side-effect-bug

Expected Behaviour

Specifying sideEffects in package.json should indicate that the specified files contain side effects, and thus should not be removed during tree-shaking.

Actual Behaviour

Specifying sideEffects in package.json seems to have the opposite of the desired effect, causing Rollup to remove the code during tree-shaking.

This example is a reduction of a real-world issue with Rollup bundling Observable Plot.

In Observable Plot, there is a complex relationship between the global plot function and the Mark class. To avoid circular imports between mark.js and plot.js (and many other files), mutation is used to assign Mark.prototype.plot:

https://github.com/observablehq/plot/blob/eb0ca8217919e477b4a05b4177a45381c06174d9/src/plot.js#L371-L376

This solitary side effect is declared in Plot’s package.json:

https://github.com/observablehq/plot/blob/eb0ca8217919e477b4a05b4177a45381c06174d9/package.json#L46-L48

However, the sideEffects hint seems to have the opposite of the desired effect: the presence of the sideEffects hint causes Rollup to drop the assignment of Mark.prototype.plot rather than to include it!

To demonstrate this, I have made a minimal reproduction in the above git repository. The test-plot module consists of four simple JavaScript files:

plot.js

import {Mark} from "./mark.js";

export function plot({marks = []} = {}) {
  return `plot(${marks})`;
}

Mark.prototype.plot = function () {
  return plot({marks: [this]});
};

mark.js

export class Mark {
  toString() {
    return "Mark";
  }
}

dot.js

import {Mark} from "./mark.js";

export class Dot extends Mark {
  toString() {
    return "Dot";
  }
}

index.js

export {plot} from "./plot.js";
export {Mark} from "./mark.js";
export {Dot} from "./dot.js";

When the sideEffects hint is include in test-plot’s package.json, the resulting bundle looks like this:

class Mark {
  toString() {
    return "Mark";
  }
}

class Dot extends Mark {
  toString() {
    return "Dot";
  }
}

const dot = new Dot();

console.log(dot.plot());

As you can see, the Mark.prototype.plot method is missing, and thus the code crashes. If the sideEffects hint is removed and the code is re-built like so:

yarn install --force && yarn test

Then the bundle changes to the expected output:

class Mark {
  toString() {
    return "Mark";
  }
}

function plot({marks = []} = {}) {
  return `plot(${marks})`;
}

Mark.prototype.plot = function () {
  return plot({marks: [this]});
};

class Dot extends Mark {
  toString() {
    return "Dot";
  }
}

const dot = new Dot();

console.log(dot.plot());

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions