Code that executes at import time bypasses the consumer's control plane. A top-level fetch() fires before the consumer has configured anything; a top-level console.log pollutes test output and server logs; a globalThis mutation can conflict with other packages or the host environment. ISO 25010 compatibility and fault-tolerance both degrade when a package modifies global state the moment it's imported. In server-side rendering, side-effecting imports can crash the render pass. In test environments, they produce flaky behavior depending on import order.
Critical because side effects on import execute before the consumer opts in, enabling silent global mutations, unsolicited network calls, and SSR crashes that are invisible until production.
Move all initialization into exported constructors or factory functions. Nothing should run at module evaluation time.
// Before — side effects on import:
console.log('SDK loaded')
globalThis.__SDK = true
const config = await fetch('/api/config') // this fires on every import
// After — no side effects, consumer controls initialization:
export class Client {
constructor(private config: ClientConfig) {
// initialization here, when the consumer asks for it
}
}
Also declare in package.json:
{ "sideEffects": false }
This tells webpack, Rollup, and Vite that all modules are safe to tree-shake. If any files have legitimate side effects (CSS imports, polyfills), list only those files in the sideEffects array.
ID: sdk-package-quality.api-surface.no-side-effects
Severity: critical
What to look for: Count all module-level statements in the SDK entry point and public modules. For each, classify whether it causes side effects on import. examine the main entry point file and any files it imports (follow the import chain). Look for code that executes at import time:
fetch(), axios(), or HTTP callsconsole.log(), console.warn(), or other loggingaddEventListener() or DOM manipulationsetInterval() or setTimeout()global, window, process, or globalThisimport './polyfill', import './register')package.json for "sideEffects": false declaration.Pass criteria: Importing the package does not execute any observable side effects. All functionality is behind exported functions or class constructors that must be explicitly called. The package may declare "sideEffects": false in package.json (recommended but not required for pass) — 100% of imports must be side-effect-free unless explicitly documented. Report: "X module-level statements found, Y are side-effect-free."
Fail criteria: The entry point or its immediate imports execute code at module evaluation time — logging, network requests, global mutations, polyfills, or event listeners that run when someone does import 'your-package'.
Skip (N/A) when: The package is explicitly designed to be a polyfill, shim, or registration side-effect (e.g., @babel/register, CSS reset packages). The package.json description or README must indicate this intent.
Detail on fail: "src/index.ts has a top-level console.log('SDK initialized') and sets global.__SDK_VERSION. These execute when the package is imported, before the consumer has opted in. A fetch() call to the package telemetry endpoint also runs at import time."
Remediation: Side effects on import surprise consumers, break tree-shaking, and cause problems in test environments and server-side rendering.
// Before — side effects on import:
console.log('MySDK v1.0.0 loaded')
globalThis.__MY_SDK = true
const config = await fetch('/api/config')
export class Client { /* ... */ }
// After — no side effects, everything is explicit:
export class Client {
constructor(config: ClientConfig) {
// initialization happens here, when the consumer asks for it
}
}
export function getVersion() {
return '1.0.0'
}
Also add to package.json:
{ "sideEffects": false }