You can use this package to see if importing a given ES module has side effects, and where they come from.
Minimizers (UglifyJS, Terser, etc) used together with bundlers (Webpack, Rollup, etc) are able to drastically reduce the size of code bundles by removing unused code. This is desirable because less code means faster startup time on both Node and Browser platforms.
But sometimes these tools cannot know if a certain piece of code is actually unused, and safe to be removed. The most common case is imported code with side effects.
Side effects in the context of importing ES modules means code that runs, and has some sort of side effect, when importing a module.
An obvious example of a side effect is top level function calls, like logging. If you have console.log('something')
on the top level of a module, that code will be retained.
Similarly, if you call myFunction()
on the top level and static analysis cannot determine that call to have no effect, the code will be retained.
A more subtle side effect is property read, like const obj = {}; obj.prop;
. obj
isn't really used, and it's not even exported. But because something might be happening on the property read, it's retained in the final bundle.
It's incommon to have size effects on property read and for that reason some tools offer a configuration option to assume property reads have no side effects.
These examples are trivial but on complex pieces of software you will likely find non-trivial variations of the same theme.
And since code is highly interconnected, it's easy to have a lot of code retained by only a few unexpected side effects.
In an ideal scenario, importing a library but not using it means no code is retained from that library. But more often than not, importing a library has side effects that can't be removed at all.
This tool was created to help identify what code is leftover from importing an unused library by trying to eliminate as much code from it as possible.
It implements that idea by following these steps:
-
create a temporary file that imports the modules you want to test
-
setup Build Optimizer to
- mark all toplevel function calls as free from side effects
- convert known TypeScript generated code with side effects to the equivalent without side effects
-
setup Terser to remove remove comments
-
run Rollup over that file with tree shaking turned on
First install this either globally or locally from npm
.
npm install --global check-side-effects
Running this tool with a path will print out to the console the remaining code with side effects. You can list multiple paths one after the other too.
check-side-effects ./path/to/library/module.js
check-side-effects ./path/to/library/module.js ./path/to/another-library/module.js
Please note that this tool is meant to check individual ES modules. Passing in a library name won't work. You have to give a relative path to a .js file containing with ES module code.
You can also pass the --output
argument to output to a file instead. Doing this will also output sourcemaps, which you can use to trace where the code came from.
check-side-effects ./path/to/library/module.js --output side-effects.js
http://sokra.github.io/source-map-visualization/ is a great way to visualize source map locations.
Below is a list of all available CLI options:
--help Show the help message.
--cwd Override working directory to run the process in.
--output Output the bundle to this path. Useful to trace the sourcemaps.
--property-read-side-effects Assume there are side effects from property reads. [Default: true]
--resolve-externals Resolve external dependencies. [Default: false]
--print-dependencies Print all the module dependencies. [Default: false]
--use-build-optimizer Run Build Optimizer over all modules. [Default: true]
--use-minifier Run minifier over the final bundle to remove comments. [Default: true]
--warnings Show all warnings. [Default: false]
--test Read a series of tests from a JSON file. [Default: false]
If you want to check against expected side effects you can use the check-side-effects --test side-effects.json
option, where side-effects.json
has the format below:
{
"tests": [
{
"esModules": "./path/to/library/module.js",
"options": {},
"expectedOutput": "./path/to/expected-output.js"
}
]
}
esModules
accepts a string or array of stringsexpectedOutput
is a path to the expected output.options
accept the same options as the CLI, but in Camel Case.
You can also pass the --update
flag to update the expected outputs for failing tests.
You can also use this tool via the JavaScript API.
This API provides you with more options than the CLI usage.
import { checkSideEffects } from './checker';
const cwd = process.cwd
const opts = {
cwd = process.cwd(),
esModules, // string or string array
output,
propertyReadSideEffects = true,
globalDefs = {},
sideEffectFreeModules = [''], // empty string assumes all modules are side effect free.
resolveExternals = false,
printDependencies = false,
useBuildOptimizer = true,
useMinifier = true,
warnings = false,
};
const result = await checkSideEffects(opts);
You can find a TSLint rule to detect toplevel property access in the tslint-no-toplevel-property-access
npm package.
You can use it by adding the path below to rulesDirectory
and the no-toplevel-property-access
rule. Path fragments to include are optional and, if omitted, all TS files will be checked.
{
"rulesDirectory": [
"node_modules/tslint-no-toplevel-property-access/rules"
],
"rules": {
"no-toplevel-property-access": [
true,
"/path/fragments/to/include/",
"/another/path/fragment/to/include/"
]
}
}
To build, run npm run build
.
To test, run npm run test
. If you need to update the test snapshots, run cd test && npm test -- --update
.
To release, run npm run release <release-type>
where <release-type>
is one of patch
, minor
or major
.