How to kill tree shaking in Webpack with static properties?
Tags: react, webpack
Published: 2020-05-14 12:10:48

Modern JavaScript bundlers like [rollup.js](https://rollupjs.org/) and [Webpack](https://webpack.js.org/) support the great feature that allows to decrease output bundle size and it's called [tree shaking](https://webpack.js.org/guides/tree-shaking/) 🌲

> Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup.

Usually it works like a charm, but sometimes something can go wrong... 🤭

### Preface the problem 📖

The common pattern for React components is to have some static properties like `defaultProps`, `propTypes`, etc.

```jsx
function Button () {
  return <button />
}

Button.defaultProps = { primary: true }
```

In our case (I am working on a UI library called [Fluent UI React](https://github.com/microsoft/fluentui)) we also have another static properties like `className`, `create` and few others. So what can go wrong? 🤔

Let's assume that we have such component:

```jsx
function Button() {
  return null;
}

Button.defaultProps = {}; // This line breaks everything 💣

export const buttonClassname = "ui-button";
export default Button;
```

And then we just want to import `buttonClassName` variable from it:

```js
import { buttonClassname } from "./Button";
console.log(buttonClassname)
```

Let's compare the output produced with Webpack 4 with and without that line:

![Comparison of Webpack bundles](https://dev-to-uploads.s3.amazonaws.com/i/lnt4yxxvqc38k56g1jn6.jpg)

**Spoiler alert:** Rollup handles this properly which can be checked in an [interactive playground](https://rollupjs.org/repl/?version=2.10.0&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMCU3QiUyMGJ1dHRvbkNsYXNzbmFtZSUyMCU3RCUyMGZyb20lMjAnLiUyRkJ1dHRvbiclM0IlNUNuJTVDbmNvbnNvbGUubG9nKGJ1dHRvbkNsYXNzbmFtZSklMjIlMkMlMjJpc0VudHJ5JTIyJTNBdHJ1ZSU3RCUyQyU3QiUyMm5hbWUlMjIlM0ElMjJCdXR0b24uanMlMjIlMkMlMjJjb2RlJTIyJTNBJTIyZnVuY3Rpb24lMjBCdXR0b24oKSUyMCU3QiU1Q24lMjAlMjByZXR1cm4lMjBudWxsJTNCJTVDbiU3RCU1Q24lNUNuQnV0dG9uLmRlZmF1bHRQcm9wcyUyMCUzRCUyMCU3QiU3RCUzQiU1Q24lNUNuZXhwb3J0JTIwY29uc3QlMjBidXR0b25DbGFzc25hbWUlMjAlM0QlMjAlNUMlMjJ1aS1idXR0b24lNUMlMjIlM0IlNUNuZXhwb3J0JTIwZGVmYXVsdCUyMEJ1dHRvbiUzQiUyMiUyQyUyMmlzRW50cnklMjIlM0FmYWxzZSU3RCU1RCUyQyUyMm9wdGlvbnMlMjIlM0ElN0IlMjJmb3JtYXQlMjIlM0ElMjJlcyUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE) that I have created.

This issue is well described in [webpack/webpack#8308](https://github.com/webpack/webpack/issues/8308) and short outcome is that:

> Code like this `g1.staticProperty2 = 'prop2';` can actually cause side-effects, if setters are registered.

### Solution 💡

For classes it can be fixed by usage of [babel-plugin-no-side-effect-class-properties](https://www.npmjs.com/package/babel-plugin-no-side-effect-class-properties) which moves class properties definition to IIFE:

```js
export default class Button {
  static className = 'ui-button';
}
// will be compiled to ➡️
var Button = /*#__PURE__*/function () {
  var Button = /*#__PURE__*/function Button() {
    _classCallCheck(this, Button);
  }
  Button.className = 'ui-button';
  return Button;
};
 
export default Button;
```

In this case there will be no side effects as static properties now defined inside of IIFE.

*What about functions?* I haven't found any ready to be used solution yet. On our side we are still discussing the proper solution. To immediately fix this issue the following workaround can be applied:

```js
const Button = (function () {
  const Button = function Button() {
    return null;
  };

  Button.defaultProps = {};
  return Button;
})();

export const buttonClassname = "ui-button";
export default Button;
```

But it's obviously too hard to scale this approach. However, for React components it may be solved in a different way as there are two common static properties:
- `defaultProps` can be fixed in two ways: for class components the Babel plugin can be used, for functional component I suggest to inline them in `props` destructuring as React team is going to [deprecate them](https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md)
- `propTypes` can be removed from production bundles via [babel-plugin-transform-react-remove-prop-types](https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types).

This post can be considered as a followup for library authors, for example [Downshift.js](https://github.com/downshift-js/downshift/pull/850) met this issue previously.

#### Webpack 5?

![Output by Webpack 5](https://dev-to-uploads.s3.amazonaws.com/i/q8vn7j22ecmdqvd0h4mm.jpg)

I also tried the sample with `webpack@5.0.0-beta.16` and it's the case there as well because Webpack relies on [Terser](https://terser.org/) for dead code minification.

----

As conclusion I would like to advice library authors rely on their tools as modern JavaScript toolkit is really powerful. *But*, at the same time keep your eye on produced bundle size 🦅 [Bundlephobia](https://bundlephobia.com/) and [webpack-bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer) can help you there 👋