bun/no-side-effects-on-import

Disallow top-level statements that call something or assign to a property

A file that does something at its top level does it for everyone who imports it. A test that imports one helper from the file also starts the server, and a bundler has to keep the file even if nothing from it is used. Export a function instead, and let the importer decide when to call it.

The rule reports a statement that isn't inside a function if it calls something, uses new, import(), or a tagged template, or assigns to, deletes, or increments a property.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"bun/no-side-effects-on-import" = "error" # or "warn"

Every option, set to its default. The ones that are commented out are unset by default.

bunfig.toml
[lint.rules]
"bun/no-side-effects-on-import" = ["error", {
  # allow = <list of strings>,
}]

Examples#

example.js1 problem
import { createServer } from "./server.js";

createServer().listen(3000);
example.js1 problem
globalThis.appVersion = "1.4.0";
example.jsNo problems
import { createServer } from "./server.js";

export function start() {
  createServer().listen(3000);
}

Declarations aren't checked, so a module can still build its constants. require() and assigning to module.exports or exports are allowed too:

example.jsNo problems
require("./polyfills.js");

const cache = new Map();

module.exports = { cache };
exports.size = 100;

A call in the assigned value is still reported:

example.js1 problem
module.exports = createApp();

Options#

OptionDefaultWhat it does
allowafter, afterAll, afterEach, before, beforeAll, beforeEach, describe, expect, it, jest, mock, suite, test, viNames that can be called at the top level. It replaces the default list.

The default list covers the functions that declare tests, hooks, and mocks, so test files pass:

example.jsNo problems
import { expect, mock, test } from "bun:test";

mock.module("./database.js", () => ({ connect: mock() }));

test.each([1, 2])("is positive", value => {
  expect(value).toBeGreaterThan(0);
});

A plain name such as "test" allows everything that starts with it, like test() and test.each()(). A name with a dot such as "customElements.define" only allows that call:

example.jswith { "allow": ["customElements.define"] }No problems
customElements.define("user-card", UserCard);
example.jswith { "allow": ["customElements.define"] }1 problem
customElements.upgrade(document.body);

When not to use it#

An entry point, a script, or a polyfill exists to do something when it runs. Turn the rule off for those files.