What Does Webpack 4 Expect From A Package With sideEffects: false

24,889

Solution 1

Sean from the webpack Team! I'll do my best in lieu of our documentation still in progress to answer your question here!

According to the ECMA Module Spec (I'm not going to try and find the link so you'll have to trust me here because it's buried),

whenever a module re-exports all exports, (regardless if used or unused) they need to be evaluated and executed in case one of those exports created a side-effect with another.

For example I've created a small scenario with a photo to better visualize the case:

In this photo we see three modules imported into a single file. The imported modules are then re-exported from that module:

Example of No Side Effects from Re-exported Modules

You can see here that none of the re-exports are effected by each other, therefore (if webpack was given a signal), we could omit exports b and c from even being traced or used (size and build time performance benefits).

enter image description here

However in this case, we see that exports c is "effected" by local state changes because it is reassigned to the summation of b and a. Therefore, (which is why the spec calls for this), we would need to include both b and a and any of its dependencies into the bundle.

We chose "sideEffects: false" as a way to save on both compile time and build size because this allows us to instantly prune (explicitly) exports that developers/library authors know are side-effect free (at the expense of a property in a package.json, or 2-3 more lines of config).

Although technically this example is very primitive, when you start dealing with Frameworks or Libraries that re-export a bunch of modules up to a higher level for Developer Experience (Three.js, Angular, lodash-es, etc), then performance gains are significant when (if they are sideEffect free module exports) you flag them in this manner.

Additional Clarifications:

  1. Obviously side-effects means something particular in fp and would include logging (console or elsewhere) and the throwing of errors. I'm assuming in this context these are perfectly acceptable?

In the case that this is trying to solve, yes. As long as effects created against module exports aren't effected by others that would cause pruning to not be acceptable.

  1. Can a module contain circular references and still use sideEffects: false?

It should, in theory.

  1. Is there any way to verify or that a module is able to use sideEffects: false beyond trying to track down errors caused by its misuse?

Not that I know of, however this would be a great tool.

  1. Are there any other factors that would prevent a module from being able to use sideEffects: false?

If the property is not in package.json or defined in module.rules, or mode: production is not set (which leverages the optimization).

Solution 2

This sideEffects setting is very vague and is not described sufficiently in the docs. The docs are mostly like "there's a sideEffects flag for modules free of any side effects".

The consensus is that "has no sideEffects" phrase can be decyphered as "doesn't talk to things external to the module at the top level".

My current understanding is that this sideEffects flag is only for "re-exports", a "re-export" being:

export { a } from './lib/a'
export { b } from './lib/b'

somewhere in <npm-package>/index.js (or any other file inside the <npm-package>).

If Webpack detects that the application only imports a from <npm-package>, and doesn't import b anywhere, then Webpack can simply drop the export { b } from './lib/b' line from <npm-package>/index.js resulting in not including './lib/b.js' file in the resulting bundle (which makes it smaller by the size of './lib/b.js' file).

Now, if './lib/b.js' had some top-level lines of code doing some "side effects", i.e. if './lib/b.js' did something like:

  • window.jQuery = ...
  • if (!global.Set) global.Set = require('babel-polyfill').Set
  • new XmlHttpRequest().post('/analytics', data)

then './lib/b.js' would be said to have "side effects" because its top-level code (which gets executed upon import './lib/b') affects something outside the scope of the './lib/b.js' file.

At the same time, as long as './lib/b.js' top-level code doesn't reach outside that *.js file then it's not having any "side effects":

let a = 1
a = a + 1 + computeSomeValue()
export default a
export const b = a + 1
export const c = b + 1

these are all not "side effects".

And there's a final gotcha: if an npm package has any *.css files that a user can import then these *.css files are all "side effects", because:

import 'npm-package/style.css'

has no variable assigned to this import which effectively means "this imported module is not used anywhere in the application" for Webpack. And so Webpack simply discards the 'npm-package/style.css' file from the bundle as part of the "tree-shaking" process if the npm-package has sideEffects: false flag. So, instead of writing sideEffects: false always write "sideEffects": ["*.css"]. Even if your npm package doesn't export any CSS files it might do so in some future and this will guard against the aforementioned "CSS file not included" bug.

Share:
24,889
Undistraction
Author by

Undistraction

Updated on November 17, 2020

Comments

  • Undistraction
    Undistraction over 3 years

    Webpack 4 has added a new feature: it now supports a sideEffects flag in the package.json of the modules it is bundling.

    From Webpack 4: released today

    Over the past 30 days we have worked closely with each of the frameworks to ensure that they are ready to support webpack 4 in their respective cli’s etc. Even popular library’s like lodash-es, RxJS are supporting the sideEffects flag, so by using their latest version you will see instant bundle size decreases out of the box.

    From Webpack docs

    The "sideEffects": false flag in big-module's package.json indicates that the package's modules have no side effects (on evaluation) and only expose exports. This allows tools like webpack to optimize re-exports.

    Whilst the second link shows the results of using the flag, it doesn't clearly explain what constitutes a side-effect. ES6 includes the concept of side-effects for modules as outlined here, but how does this relate to what Webpack considers side-effects.

    In the context of the sideEffects flag, what does a module need to avoid to use sideEffects:false without issues, or conversly, what does a module need to do in order to use sideEffects:false without issues.

    For completeness, despite @SeanLarkin's solid answer below, I would love to get clarification on the following:

    1. Obviously side-effects means something particular in fp and would include logging (console or elsewhere) and the throwing of errors. I'm assuming in this context these are perfectly acceptable?

    2. Can a module contain circular references and still use sideEffects: false?

    3. Is there any way to verify or that a module is able to verify that a module can sideEffects: false beyond trying to track down errors caused by its misuse?

    4. Are there any other factors that would prevent a module from being able to use sideEffects: false?

  • Undistraction
    Undistraction about 6 years
    Thanks very much. Please could you clarify the following for completeness: 1. Obviously side-effects means something particular in fp and would include logging (console or elsewhere) and the throwing of errors. I'm assuming in this context these are perfectly acceptable? 2. Can a module contain circular references and still use sideEffects: false? 3. Are there any other factors that would prevent a module from being able to use sideEffects: false 4. Is there any way to verify or that a module is able to use sideEffects: false beyond trying to track down errors caused by its misuse?
  • Sean Larkin
    Sean Larkin about 6 years
    1. In the case that this is trying to solve, yes. As long as effects created against module exports aren't effected by others that would cause pruning to not be acceptable. 2. It should, in theory. 3. If the property is not in package.json or defined in module.rules, or mode: production is not set (which leverages the optimization). 4. Not that I know of, however this would be a great tool.
  • Undistraction
    Undistraction about 6 years
    Thanks. I've added as an addendum to your answer to make it easier to discover.
  • catamphetamine
    catamphetamine about 6 years
    What is the exact algorythm for Webpack determining what exactly does it drop when it eliminates an "unused export"? Will it drop only those imports which are "direct" re-exports? Or will it perform some heuristics? E.g. inspect which imports are being used inside the export being dropped and perhaps drop those imports from the code.
  • Sean Larkin
    Sean Larkin about 6 years
    Simply run webpack with mode: none set in your config file, and you can see the annotations we make that show what we are tracking. We leverage some of this.
  • catamphetamine
    catamphetamine about 6 years
    I updated my aswer with what's my current understanding is.
  • Sean Larkin
    Sean Larkin about 6 years
    Specifically I meantion sideEffects against eachother, which would force one to be pulled in even if it wasn't explicitly used. This isn't sideEffects in the function programming reference.
  • kimamula
    kimamula almost 6 years
    Does sideEffects: false also work inside the project in which it is set? It seems it doesn't. I mean, if I have, for example, a.js whose contents is export const foo = Promise.resolve('foo'); export const bar = 'bar'; and b.js whose contents is import { bar } from './a.js'; console.log(bar); and build them with specifying b.js as an entry, the resulting bundle contains Promise.resolve("foo") even if I specify sideEffects: false in package.json.
  • kimamula
    kimamula almost 6 years
    I was misunderstanding how sideEffects: false works and the the above example is not correct. I confirmed sideEffects: false also works inside the project in which it is set using the example of the original answer. Thanks!
  • mesqueeb
    mesqueeb over 4 years
    @SeanLarkin How do I know if dependencies (albeit marked "external") have side effects? Can I still mark my own package sideEffects: false even though its dependencies might have side-effects?
  • Hinrich
    Hinrich almost 4 years
    exclude some files with e.g. sideEffects: ["*.css"] if you want to exclude some files from being marked as side-effect free. see @catamphetamine 's answer for details on this.
  • ronnyrr
    ronnyrr over 3 years
    Any thoughts on using side effects together with TS? Get a lot of erros on TS typing parts when adding the package.json with sideEffects in every folders re-exporting files
  • darKnight
    darKnight almost 3 years
    @SeanLarkin: Why can't Webpack determine the dependancies on its own without needing any hints? I mean, Webpack bundles all code by recursively going through imports, and also eliminates dead code with a similar process (tree shaking). So if all modules are ESM, what's stopping Webpack to figure out on its own if a module has a side-effect or not?
  • VimNing
    VimNing about 2 years
    So, is it correct to say that: I don't need to add sideEffects: false and/or webpack config's optimization.sideEffects: true to enable tree-shaking?